Přejít k navigační liště

Zdroják » Různé » Podepisování PDF přes eObčanku na Macu? Napsal jsem si vlastní nástroj.

Podepisování PDF přes eObčanku na Macu? Napsal jsem si vlastní nástroj.

Články Různé

Jako začínající podnikatel potřebuji pravidelně podepisovat smlouvy kvalifikovaným podpisem. Certifikát jsem si uložil na čip eObčanky, jenže se ukázalo, že použitelný podepisovací software pro macOS prakticky neexistuje. Profesní vztek programátora vedl ke vzniku EasySigneru — bezplatné multiplatformní aplikace na .NET 8 a Avalonia UI. V tomto článku najdete nejen příběh, ale i technické detaily: jak v praxi vypadá práce s PKCS#11, jak je postaven řetězec PAdES podpisu od B-B po B-LTA a na jaké hrábě jsem cestou šlápl.

Trocha historie: čip v občance a slepá ulička se softwarem

Jsem obyčejný programátor a s elektronickým podpisem jsem nikdy neměl nic společného. Vedle zaměstnání jsem ale začal podnikat — a najednou bylo potřeba pravidelně podepisovat smlouvy a plné moci. Certifikát v souboru na disku mi nevyhovoval: notebook občas používají i jiní lidé. Takže hardwarový token, ze kterého privátní klíč nikdy neodejde. Jenže ztratit flashku navíc je pro mě to nejjednodušší na světě.

Řešení se našlo skoro náhodou: jako úložiště certifikátu a privátního klíče lze použít čip ve vlastním občanském průkazu. Občanku mám vždy u sebe. Koupil jsem kvalifikovaný certifikát u PostSignum (existují i jiní poskytovatelé, tohle je jen jeden z dostupných), nejlevnější čtečku karet — a klíč na čip zapsal překvapivě snadno pomocí aplikace eObčanka, která je dostupná na webu státní správy. A malá odbočka dopředu: v programu lze použít jak libovolný token dostupný na trhu, tak certifikát uložený v souboru.

Pak to začalo být zajímavé. Online služby jsem vyloučil hned — osobní smlouvy na cizí servery kvůli podpisu nahrávat nebudu. Desktopový software měl buď nesmyslnou cenu, nebo předplatné. Kývl jsem i na PostSignum+, jen jsem si zapomněl přečíst drobnost: existuje pouze pro Windows. A já mám MacBook Air. Dočasným řešením se stal Acrobat Reader: podepisovat se s ním dá, ale postup je zamotaný a spojení s tokenem se pravidelně ztrácelo — musel jsem si napsat shell skript na opětovné připojení čtečky. Pro běžného uživatele je to prakticky neudržitelné. V tu chvíli se ve mně probudil profesní vztek: jsem přece programátor.

Architektonická rozhodnutí: co postavit a co koupit

Plán byl prostý: aplikace, která se nastaví v pár krocích, vypadá a funguje stejně na Windows i macOS a zvládne ji člověk bez speciálních znalostí.

Stack jsem vybral rychle: C#, .NET 8 a Avalonia UI — multiplatformní XAML framework. Z jednoho kódu se sestaví self-contained jednosouborová aplikace (PublishSingleFile + SelfContained), takže uživatel nemusí instalovat .NET runtime — pro netechnické publikum zásadní věc. Navíc: Visual Studio Community Edition — zdarma, Avalonia — zdarma, Pkcs11Interop — zdarma. Fakticky jediná komerční knihovna a můj neocenitelný čas 😊.

Nejdůležitější rozhodnutí — nepsat si práci s PDF a kryptografii podpisu sám. Formát PDF je plný historických nuancí a chyba v implementaci podpisu znamená právní důsledky, ne jen bug. Koupil jsem proto komerční knihovnu, která umí PDF renderovat i vytvářet PAdES podpisy všech profilů. Ano, jsou to peníze z vlastní kapsy na aplikaci zdarma, ale šel jsem do toho vědomě: platí staré dobré „don’t roll your own crypto“ — vlastní implementace je ta nejhorší možná úspora

Knihoven pro práci s PDF a kryptografií je na trhu poměrně dost. A vždycky jsou to velké peníze — je to nika zaměřená především na komerční sektor.

Před nákupem jsem si samozřejmě udělal průzkum. iText je faktický standard oboru s plnou podporou PAdES a formálně je zdarma, jenže pod licencí AGPL: pro aplikaci s uzavřeným kódem to znamená buď zveřejnit kompletní vlastní zdrojáky, nebo koupit komerční licenci, jejíž ceny se nezveřejňují — jen individuální kalkulace přes obchodní oddělení. Pro víkendový projekt slepá ulička. Aspose.PDF for .NET začíná zhruba na 1 175 dolarech za vývojářskou licenci, jenže tato základní úroveň nedovoluje distribuovat aplikaci třetím stranám — pro distribuci koncovým uživatelům jsou potřeba výrazně dražší úrovně OEM/SDK. A to je cena jen jednoho komponentu z jejich nabídky. Bezplatný PDFsharp poctivě podporuje základní CMS podpis přes rozhraní IDigitalSigner, ale PAdES profily s DSS, OCSP/CRL a dokumentovými razítky bych si musel implementovat sám — tedy přesně to, čemu jsem se chtěl vyhnout. Díval jsem se i na SecureBlackbox a přemýšlel o jiných vývojových platformách (pracuji s produkty Embarcadero), ale všechno jsou to tisíce dolarů a některé o řád víc.

Knihovna, kterou jsem nakonec vybral — GemBox.Pdf — stála 890 dolarů jednorázově: trvalá licence na vývojáře, royalty-free distribuce bez jakýchkoli omezení a podpora všech profilů PAdES i PKCS#11 rovnou z krabice. A ještě mi dali slevu.

PKCS#11 v praxi: dvě knihovny a opatrnost s PINem

S tokenem aplikace komunikuje přes standardní rozhraní PKCS#11 — pro eObčanku je to systémová knihovna eOPproxyP11 (v systému je už po instalaci aplikace eObčanka). Zajímavé je, že na token nakonec sahají dvě různé knihovny. Pro inventarizaci — ukázat uživateli, jaké certifikáty na čipu jsou — používám open source Pkcs11Interop: otevřu read-only session, přihlásím se jako CKU_USER, najdu objekty třídy CKO_CERTIFICATE a přečtu atributy CKA_VALUE a CKA_ID. Samotný podpis pak provádí PKCS#11 modul komerční PDF knihovny. Zní to divně, ale rozdělení se ukázalo jako praktické: seznam certifikátů je UI logika, podpis je atomická operace uvnitř PDF řetězce.

Odsud první hrábě: jak „spárovat“ certifikát vybraný při inventarizaci s identifikátorem uvnitř podpisové knihovny? Labely tokenů a sériová čísla zrazují — spolehlivé se ukázalo jen bajtové porovnání surových dat certifikátu (raw DER). Token hledám podle labelu, certifikát na něm podle úplné shody bajtů.

Druhé poučení — PIN. Tři neúspěšné pokusy a token je zablokovaný, a s ním v případě občanky i doklad totožnosti. Chyby PKCS#11 je proto potřeba rozlišovat přísně: CKR_PIN_INCORRECT se zpracovává zvlášť a uživateli se ukazuje lidskou řečí, ne jako obecná výjimka — aby nemačkal „zkusit znovu“ naslepo. A principiální rozhodnutí: PIN se nikdy neukládá na disk, žije jen v paměti procesu do zavření aplikace.

Řetězec PAdES: tři průchody dokumentem

EasySigner podporuje všechny základní profily PAdES: B-B, B-T, B-LT a archivní B-LTA. Implementace vyústila v třífázový řetězec a každá fáze znamená kompletní znovunačtení dokumentu:

Fáze 1 — samotný podpis (formát CAdES uvnitř PDF). Zde se také vytváří viditelné pole podpisu s grafikou: text, datum, lokalita a volitelně obrázek vlastnoručního podpisu. Pro B-T a vyšší se k podpisu rovnou připojí časové razítko RFC 3161 z nakonfigurovaného TSA serveru. Překvapení, které mě stálo večer debugování: volání Sign() podpis jen připraví, skutečná kryptografie a síťová komunikace s TSA probíhá až během Save(). Chyby sítě tak přilétají úplně jinde, než je čekáte.

Fáze 2 — pro B-LT se dokument načte podruhé a do jeho Document Security Store se přidají validační informace: odpovědi OCSP a CRL pro celý řetěz certifikátů. Právě to umožňuje ověřit podpis po letech, kdy už certifikát neplatí.

Fáze 3 — pro B-LTA třetí průchod přidá dokumentové časové razítko nad všechno. Celý řetězec běží v MemoryStream a na disk se výsledek zapíše přesně jednou — soubor s příponou „_signed“ ve stejné složce.

Drobné hrábě, které sežraly nejvíc času

Souřadnicový systém PDF. Uživatel myší nakreslí obdélník na náhledu stránky — souřadnice od levého horního rohu. Jenže PDF má počátek vlevo dole. Jeden vzorec (pdfY = (1 − (y + h)) × výška stránky), ale než jsem k němu došel přes stránky na šířku a škálování náhledu, podpis stihl navštívit všechny rohy dokumentu kromě toho správného.

Renderování náhledu. Stránky PDF se renderují do PNG v dvojnásobném rozlišení mimo UI vlákno a překreslování při změně velikosti okna je zabalené do debounce timeru — bez toho aplikace na velkých dokumentech „zamrzala“ při každém pohybu okraje okna.

Multiplatformní ukládání tajemství. Heslo k TSA se na Windows šifruje přes DPAPI (ProtectedData, scope CurrentUser). Na macOS ale DPAPI není — a poctivá odpověď: zatím je tam jen omezení přístupových práv ke konfiguračnímu souboru (chmod 600). Integrace s Keychain je v plánu. Multiplatformnost .NET končí přesně tam, kde začínají systémová úložiště tajemství.

Balení pro macOS. .NET sám o sobě .app bundle nesestaví, takže samostatný MSBuild target po publish poskládá strukturu Contents/MacOS, vygeneruje minimální Info.plist a bundle složí ručně. Bez podpisu a notarizace vítá uživatele Gatekeeper varováními — notarizace je v plánu, pokud bude zájem komunity.

Co dál

Zdrojové kódy zatím nezveřejňuji — kvůli zakoupeným komerčním knihovnám. Pokud bude zájem komunity, zveřejním kód bez licenčních klíčů: půjde zkompilovat, jen nebude plně funkční. Aplikace je zdarma, bez jakýchkoli omezení a lokalizovaná do  češtiny, angličtiny.

Binárky pro Windows i macOS najdete v repozitáři EasySigner na GitHubu. Komu bude nástroj užitečný, může přispět dobrovolným darem — odkaz je v sekci About. Tohle je první verze: to, co jsem potřeboval já, tak, jak to vidím já. Budu čekat na ohlasy, co přidat nebo změnit.

V navazujícím článku se chci ponořit hlouběji do práce s GemBox.Pdf a Pkcs11Interop — s kódem a konkrétními příklady.

V hlavním zaměstnání C# nepoužívám a s .NET vůbec nepracuji, takže to pro mě byla velmi zajímavá zkušenost. Jsem si jistý, že profesionálové by to udělali (a dělají) mnohem líp. Budu vděčný za odborné komentáře a rady.

Komentáře

Odebírat
Upozornit na
guest
1 Komentář
Nejstarší
Nejnovější Most Voted
Jan Andrle

… reposted this!

Jan Andrle

@zdrojak bylo by i pro linux (asi AppImage)?