Clevis podruhé: TPM a SSS a k čemu je dobrý SecureBoot

V návaznosti na předchozí článek si dnes ukážeme, jak použít Clevis na to, co se běžně používá jinde a jak moc dobrý nápad je to používat... Na jednom nejmenovaném systému je v nastavení položka na zapnutí šifrování pevného disku. To se zapne, ono se to zašifruje a uživatele se to na nic ani neptá... Takže, jak to funguje?

Klíčem je TPM zařízení na základní desce počítače. Což je zařízení, do kterého si operační systém uloží klíč... a pak, když si o něj někdy zase řekne, tak mu to klíč vydá. V podání toho případu předtím doopravdy, když se to šifrování zapne, tak počítač normálně nabíhá, nechce žádné heslo, ale při vyndání disku z počítače a umístění jej do USB rámečku nebo jinam jsou data šifrována. Rozbor na to, jak moc to je dobrý nápad, si necháme na potom. Ale můžeme si to zkusit napřed rozchodit. Podle odkazovaného článku rozchodíme clevis a dracut identickým způsobem. Předpokládáme, že disk již máme šifrovaný LUKSem a máme v něm tedy slot s nějakým heslem (nejlépe nějakým složitým, dlouhým, uloženým někde... pokud tohle rozcházíme, předpokládáme, že to první heslo budeme používat jen v nouzi). Uložení klíče do TPM a přidání nového keyslotu do LUKSu tedy proběhne tímto magickým příkazem (disk a partition vyměníme za tu správnou):

clevis luks bind -d /dev/nvme0n1p3 tpm2 '{"pcr_bank":"sha256","pcr_ids":"0,2,3,5,6,7"}'

V parametru pcr_ids jsou ID PCR registrů, kterými je klíč šifrován. PCR registry v sobě mají zaznamenaný nějaký stav systému (seznam registrů je třeba zde), například v registru 7 je nastavení secure bootu. To znamená, že když někdo tohle nastavení změní, změní se hodnota registru a klíč již nelze dešifrovat. Je tedy vhodné vybrat takový set registrů, aby systém zůstal dostatečně bezpečný a nevydal klíč komukoliv, ale zároveň aby ke změně obsahu registru nedocházelo při běžném používání (ztráta schopnosti obnovit šifrovací klíč znamená nutnost zadat to heslo ručně a opravit to).

Tímto jsme jinak sice k disku přidali nový šifrovací klíč, který je uložen v TPM, ale ještě taky musíme při bootu systém naučit jej využít. To provedeme (stejně jako v odkazovaném článku) úpravou dracutmodules a vygenerováním nové initrd. Do dracutmodules tedy přidáme tohle: clevis clevis-pin-tpm2

Shamir's secret sharing

Ještě jedna rychlá odbočka k tomu, co clevis taky umí. Shamir's secret sharing je algoritmus pro rozdělení klíče na n částí tak, aby při získání k částí (za předpokladu že k <=n) jsme schopni klíč složit znovu zpátky. Praktická ukázka: pokud vezmeme předchozí článek, kdy se klíč získával přes síť z Tang serveru, můžeme to klidně nakombinovat. Tímto příkazem:

clevis luks bind -d /dev/nvme0n1p3 sss '{"t": 2, "pins": {"tang": {"url": "http://192.168.1.1:8888"}, "tpm2": {"pcr_bank": "sha256", "pcr_ids": "0,2,3,5,6,7"}}}'

se část klíče vezme z Tang serveru (na adrese 192.168.1.1) a část z TPM. t je výše zmíněný parametr k, tedy v tomto případě to znamená, že k získání šifrovacího klíče pro disk jsou potřeba obě dvě části klíče - jak ta v TPM, tak ta z Tang serveru. Pokud vám tedy někdo ukradne Tang server a disk z počítače, nemá dost informací na získání klíče k dešifrování. Pokud vám ukradne celý počítač, najde chybu v nastavení a z TPM dostane uložený klíč, nemá stejně tu druhou část z Tang serveru a zase nic nedešifruje. Pokud by se t nastavilo na 1, stačí mít klíč z TPM nebo z Tang serveru.

Do dracutu je potřeba zase doplnit moduly, aby to fungovalo. Kromě toho, že je potřeba nakombinovat ty moduly pro TPM a Tang, tak je navíc potřeba i modul pro sss. Tedy pro tento příklad je potřeba přidat tam toto: clevis clevis-pin-sss clevis-pin-tang clevis-pin-tpm2

Secure Boot

Secure Boot měl být původně mechanismus na bojování proti malwaru, který se nacpe někam jako bootloader. Pokud počítač totiž jako první zavede malware, ten má pak kontrolu nad vším ostatním a z principu je pak těžké ho věcmi jako antivir, které jsou pak puštěné už pod ním, detekovat. Princip není zase tak komplikovaný, UEFI firmware v počítači po spuštění spustí nějaký zavaděč. Secure Boot pak funguje tak, že v UEFI firmwaru jsou nahrané nějaké veřejné klíče. A při tom bootu se kontroluje, že ten zavaděč, který firmware spouští, má proti tomuto uloženému klíči validní podpis. U toho nejmenovaného jediného správného systému tohle mohlo být poměrně efektivní; ten privátní klíč, kterým se ten bootloader tedy podepisuje, je uložen někde, kde se k němu běžně jen tak nikdo nedostane. Systém si pak v rámci updatů stahuje binárky, které jsou již podepsané, tyhle klíče v BIOSu typicky již jsou a tak nějak to tedy tohle splňuje. U Linuxu je to trochu komplikovanější. On ten bootovací řetězec by měl být zabezpečený celý. Že si spustím ověřený a správný bootloader je mi poměrně k ničemu, když mi ten následně nabootuje cinknutý kernel. Takže by měl být podepsaný i kernel. Během toho bootu se typicky zavede initrd, ve které jsou nějaké scripty co taky můžou dělat leccos, takže initrd by měla být podepsaná taky (což už je samo o sobě problém a jde to blbě) a dále se během bootu loadují kernel moduly, které by opět měly být podepsané... I kdybyste používali nějakou binární distribuci, která tohle všechno servíruje hotové a podepsané, narazíte při první instalaci věci typu VirtualBox / VMware workstation a podobně, které ty kernel moduly používají vlastní a při instalaci se kompilují... proti tomu aktuálnímu kernelu. Jinými slovy tím prostě vznikne modul, nově, na vašem počítači, který v tu chvíli podepsaný není. Takže skončíte u toho, že si minimálně ty kernel moduly podepisujete sami, klíčem co je nejspíše v tom počítači a tudíž jako obrana proti malwaru je to dost k ničemu, protože ten malware, když už má ty práva roota na tom počítači, aby mohl sahat někam do /bootu, tak se může úplně stejně taky podepsat.

Vraťme se ale zpátky k tomu odemykání šifrovaného disku z TPM, které je umístěné ve stejném počítači. Můžeme očekávat, že samotný boot systému by měl být bezpečný a v pohodě - systém najede do nějakého loginu, pokud tam je rozumné heslo, asi ho někdo jen tak neuhodne a k ničemu se nedostane. Jenže... v první řadě pokud máme nějaký běžný bootloader jako grub nebo systemd-boot, tak typicky obsahují konzoli, kterou se dají editovat parametry se kterými se loaduje kernel. Pokud tam někdo připíše init=/bin/bash, naběhne mu odemčená konzole s rootem a odemčeným diskem. To je blbě. Konzoli sice můžeme v nastavení bootloaderu vypnout, jenže ten konfigurák je zase typicky na tom nešifrovaném oddílu disku - když si tedy disk dáme do rámečku, můžeme konfiguraci změnit, vrazit to zpátky a nabootovat. Pokud opustíme bootloader, můžeme tam stejně tak dát jinou initrd, která nám místo initu nechá puštěný ten shell. Nebo i ten kernel žejo... A v neposlední řadě taky úplně nechceme, aby tam někdo prostě nabootoval nějaké livko a vytáhl ten klíč z TPM tím.

Takže takový nápad. Do UEFI firmwaru můžeme přidat vlastní klíče (a odstranit i ty, co tam jsou). UEFI taky umí linuxový kernel bootovat napřímo, můžeme se zbavit toho bootloaderu, který nám přináší další variabilitu jak to rozbít. Dracut který používáme na tvorbu initrd umí vytvořit (podepsaný) EFI stub - tj vezme kernel, splácá ho dohromdy s initrd, kterou vytvořil a udělá z toho jeden soubor, který podepíše a který UEFI firmware umí přímo nabootovat. Takže do UEFI vložíme vlastní klíče, těmi podepíšeme EFI stub (kernel+initrd), který má natvrdo v sobě napsané parametry pro kernel. A podepíšeme s ním i ty kernel moduly. V BIOSu pak zapneme Secure Boot a zaheslujeme jej. Jaký je důsledek? Do kernel cmdline nikdo nic nepřidá, protože tam není žádný bootloader, do kterého by to šlo psát. Když vám někdo vymění kernel, tak jej nenabootuje, protože nebude podepsaný. LiveCD taky nenabootuje, protože nebude podepsané. A pokud resetne nastavení BIOSu a vypne secure boot, tak klíč z TPM nedostane, protože se změní ty hodnoty PCR registrů, kterými jsme klíč chránili. Pokud tedy v systému není nějaká další chyba, neměl by se nikdo k datům na disku dostat - systém mu sice nabootuje, ale bez uživatelského účtu se dále nedostane.

Jak na to? Na zjednodušení toho podepisování se hodí nástroj sbctl. Příkazem sbctl create-keys si vytvoříme vlastní klíče. Secure Boot uvedeme do setup módu (u některých desek tak, že jej prostě vypneme, jinde je na to speciální menu) a příkazem sbctl status ověříme, že v setup módu jsme. Pomocí sbctl enroll-keys pak naše vygenerované klíče uložíme do UEFI firmwaru. Tímto příkazem tam uložíme jen ty naše klíče, co jsme vygenerovali. Na mých systémech to funguje, nicméně návod zmiňuje, že některé option ROM některého hardwaru jsou podepsané klíči Microsoftu a tudíž pokud klíče Microsoftu zmizí z UEFI firmwaru, přestane tenhle hardware fungovat (a bohužel to většinou jsou grafické karty, takže se pak dost debilně vypíná secure boot...). Takže to nedoporučují a místo toho doporučují enroll s tímto parametrem: sbctl enroll-keys -m - ten totiž navíc přidá kromě našich klíčů i ty od MS (což ale zase znamená, že ten bloatware od MS pak půjde nabootovat i se zapnutým secure bootem).

Pokud si kompilujeme vlastní kernel, nastavíme v menuconfigu volbu CONFIG_MODULE_SIG_KEY - tam se nastaví cesta ke klíči, kterým se to bude podepisovat. V souboru by měl být veřejný i privátní klíč, sbctl je nicméně generuje do rozdílných souborů. Vyřešil jsem to tím, že jsem si vytvořil nový soubor, kde jsem to sjednotil (cat /var/lib/sbctl/keys/db/db.{key,pem} > /var/lib/sbctl/keys/db/db_combo.pem) a /var/lib/sbctl/keys/db/db_combo.pem jsem pak nastavil v menuconfig. Na gentoo je pak dobré do make.conf přidat i tyhle volby, aby se případně podepisovaly další moduly z ebuildů:

MODULES_SIGN_KEY="/var/lib/sbctl/keys/db/db.key"
MODULES_SIGN_CERT="/var/lib/sbctl/keys/db/db.pem"
MODULES_SIGN_HASH="sha512"
SECUREBOOT_SIGN_KEY="/var/lib/sbctl/keys/db/db.key"
SECUREBOOT_SIGN_CERT="/var/lib/sbctl/keys/db/db.pem"

No a v neposlední řadě nastavíme dracut. Do konfiguračního souboru přidáme něco takového:

uefi=yes
uefi_secureboot_cert=/var/lib/sbctl/keys/db/db.pem
uefi_secureboot_key=/var/lib/sbctl/keys/db/db.key
CMDLINE=(
	ro
	rd.luks.allowdiscards
	rd.luks.uuid=xxx
	root=UUID=yyy

)
kernel_cmdline="${CMDLINE[*]}"
unset CMDLINE

První 3 volby říkají, že má dělat EFI stub a že jej má podepsat tímhle klíčem. V CMDLINE jsou pak jednotlivé volby, co se předávají kernelu a které normálně byly v grubu. Takže kromě doplnění správných UUID (šifrované partitiony a potom partitiony s odemčeným FS) doplňte všechno další, co jste tam měli (cat /proc/cmdline napoví). Použijte zase dracut stejně jako když jste tvořili initrd - jen tentokrát by měl vygenerovat celý EFI stub, který bude poměrně větší. Dracut jako další parametr bere případně cestu/jméno EFI stubu který generuje. Doporučuji používat něco statického, protože nyní ještě budeme muset pro tento stub vytvořit boot záznam. Takže řekněme, že pustíme dracut --kver 6.15.5-gentoo-x86_64 /boot/efi/EFI/Linux/gentoo.efi, čímž vygenerujeme EFI stub pro kernel 6.15.5 a uložíme jej do /boot/efi/EFI/Linux/gentoo.efi. Abychom mohli systém nabootovat, vytvoříme ještě tedy v UEFI boot záznam:

efibootmgr --create-only --disk /dev/nvme0n1 --part 1 --label "Gentoo Linux" --loader "\\EFI\\Linux\\gentoo.efi"

Disk a partition upravíme podle potřeby. Restartujeme systém, v BIOSu by se nám měla objevit nová volba Gentoo Linux, můžeme zapnout secure boot a pokud jste to udělali dobře, mělo by to nabootovat. Teď taková poznámka, na kterou dojedou ti, co napřed konají, než to dočetli do konce: pokud tohle prošlo, tak teprve teď přes clevis nastavte šifrování disku klíčem z TPM. Protože jinak při těch registrech, co byly použity v ukázce, dojde přesně teď k jejich změně a systém již nenabootuje.

Nouzový režim

Celý tenhle přístup má také jednu nevýhodu. Pokud se nám něco pokazí, tak systém nenabootujeme sami. A bohužel clevis je v tomhle trochu pitomý - pokud mu odemknutí disku z TPM neprojde (třeba proto, že se ty registry změnily a že jsme to i věděli), tak to prostě zkouší pořád dokola, ale na heslo, které jako zálohu máme stále nastavené, se nezeptá. Změnit parametry kernelu taky nemůžeme, to jsme úmyslně zakazovali. Takže reálně se pak stane, že updatnete BIOS a systém se už nepustí a musíte hledat nějaké LiveCD, ze kterého to půjde nabootovat a opravit. To je dost oser.. a ano, zrovna po updatu BIOSu se tohle skoro vždycky stane. Z toho důvodu celkem doporučuji vygenerovat si ještě záložní EFI stub bez clevis modulů. Tedy z konfigurace dracutu v modulech umažeme všechny ty clevis věci - a před dracut vygenerujeme image. Dáme si ji třeba do souboru gentoo_recovery.efi. Přes efibootmgr identicky jako v předchozím případě uděláme boot záznam (jen opravíme název položky a souboru). A pak zase vrátíme nastavení dracutu zpátky. Pokud nám tedy systém z nějakého takového důvodu nenabootuje, vybereme při startu počítače výběr boot zařízení a zvolíme tenhle záložní EFI stub. Ten se nebude pokoušet odemykat disk z TPM, ale zeptá se na heslo, které zadáme ručně a měli bysme se dostat do našeho systému. Přes clevis luks bind... pak nastavíme šifrovací klíč z TPM znovu a znovu by to mělo začít fungovat.

Recovery stub je vhodné občas vygenerovat znovu. Pokud totiž kernel v něm bude o hodně starší než ten, co se používá běžně, tak by se mohlo taky stát, že z něj nenabootuje ani tak. Protože třeba už nepozná filesystém, co máte na disku, které se od té doby někam vyvinul :-)