markdown
Vlastni-certifikacni-autorita.md
markdown
# 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](TLS-a-HTTPS), veřejné certifikáty v [certifikátech a Let's Encrypt](Certifikaty-a-Lets-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](#omezení-jmen-nameconstraints), 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](Certifikaty-a-Lets-Encrypt) 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](#mkcert-pro-vývoj) 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](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`](#caddy-tls-internal) | služby za jednou proxy | jeden řádek || [mkcert](#mkcert-pro-vývoj) | vývoj na vlastním stroji | jeden příkaz || [step-ca](#step-ca-vlastní-ca-s-acme) | skutečná vnitřní CA s automatickou obnovou | půl dne || [openssl ručně](#ručně-přes-openssl) | 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](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: ```bash# na hostiteli/var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt # v Dockerudocker 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: ```bashcaddy trust # a caddy untrust ho zase odebere``` Na ostatní zařízení ho musíš dostat [ručně](#instalace-na-koncová-zařízení). 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](#step-ca-vlastní-ca-s-acme). **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](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. ```bashapt install mkcert libnss3-tools # Ubuntu 24.04, balíček je v universemkcert -install # vyrobí CA a přidá ji do úložišť důvěrymkcert 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ě](#firefox). Vypadnou dva soubory `localhost+3.pem` a `localhost+3-key.pem` a prohlížeč je bez řečí přijme. ```bashmkcert -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ě: ```bashcurl -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: debURIs: https://packages.smallstep.com/stable/debianSuites: debsComponents: mainSigned-By: /etc/apt/keyrings/smallstep.ascEOF 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 ```bashstep 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ší: ```bashstep ca provisioner update acme --x509-default-dur=720hsystemctl restart step-ca``` ### Napojení klientů [Caddy](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ěří: ```bashREQUESTS_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: ```bashstep 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 ```bash#!/bin/bash# vytvor-ca.sh — jednorázově, kořenový klíč pak ulož mimo serverset -euo pipefail mkdir -p ca && cd caumask 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ěříš: ```bash$ openssl verify -CAfile root.crt podvrzeny-google.crterror 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é: ```bash# kořen omezený jen na permitted;DNS:domaDNS: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 ```bash#!/bin/bash# vytvor-mezilehlou.shset -euo pipefailcd caumask 077 openssl ecparam -name prime256v1 -genkey -noout -out int.keyopenssl req -new -key int.key -out int.csr -subj "/CN=Doma Issuing CA/O=Doma" cat > int.ext <<'EOF'basicConstraints=critical,CA:TRUE,pathlen:0keyUsage=critical,keyCertSign,cRLSignsubjectKeyIdentifier=hashauthorityKeyIdentifier=keyid:alwaysEOF 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 ```bash#!/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"; shiftDNY=395cd "$(dirname "$0")/ca"umask 077 # subjectAltName: hlavní jméno musí být i tady, samotné CN dnes nikdo nečteSAN="DNS:${CN}"for x in "$@"; do if [[ "$x" =~ ^[0-9.]+$ || "$x" == *:* ]]; then SAN="${SAN},IP:${x}" else SAN="${SAN},DNS:${x}" fidone openssl ecparam -name prime256v1 -genkey -noout -out "${CN}.key"openssl req -new -key "${CN}.key" -out "${CN}.csr" -subj "/CN=${CN}" cat > "${CN}.ext" <<EOFbasicConstraints=critical,CA:FALSEkeyUsage=critical,digitalSignature,keyEnciphermentextendedKeyUsage=serverAuthsubjectAltName=${SAN}subjectKeyIdentifier=hashauthorityKeyIdentifier=keyid,issuerEOF 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í: ```bash./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](Certifikaty-a-Lets-Encrypt). 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`: ```bashcat > klient.ext <<'EOF'basicConstraints=critical,CA:FALSEkeyUsage=critical,digitalSignatureextendedKeyUsage=clientAuthsubjectAltName=email:martin@domaEOF openssl ecparam -name prime256v1 -genkey -noout -out martin.keyopenssl 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#12openssl 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](Nginx): ```nginxssl_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: ```bashopenssl 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) ```bashcp root.crt /usr/local/share/ca-certificates/doma-root.crtupdate-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í: ```bashrm /usr/local/share/ca-certificates/doma-root.crtupdate-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) ```bashcp root.crt /etc/pki/ca-trust/source/anchors/doma-root.crtupdate-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í. ```dockerfile# Debian/Ubuntu základCOPY doma-root.crt /usr/local/share/ca-certificates/RUN update-ca-certificates # Alpine základ — balíček je potřeba doinstalovatCOPY 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: ```yamlvolumes: - ./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](#chrome-a-chromium-na-linuxu). ### macOS ```bashsudo 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ý. 1. Pošli si `root.crt` mailem nebo přes AirDrop a otevři ho. Telefon nabídne stažení profilu.2. **Nastavení → Obecné → VPN a správa zařízení** → nainstaluj profil.3. **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`: ```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: ```bashsnap 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: ```bashapt install libnss3-toolscertutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "Doma Root CA" -i doma-root.crtcertutil -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š: ```bash# 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](#omezení-jmen-nameconstraints) povolený — a bez něj za jakýkoliv web vůbec. ```bashchmod 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 ```bash# co je v certifikátuopenssl 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á serveropenssl 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](Certifikaty-a-Lets-Encrypt) ten problém řeší lépe, levněji a jednou provždy. A jestli přesto jdeš do vlastní CA: použij [step-ca](#step-ca-vlastní-ca-s-acme), nastav [omezení jmen](#omezení-jmen-nameconstraints) a kořenový klíč nenechávej na serveru.