Po minulém teoretickém úvodu do problematiky zabezpečení přejdeme k praxi - tedy k nastavování bezpečnostní politiky a práci se zavaděči tříd. Stranou nezůstane ani podepisování kódu.
14.3.2006 06:00 | Lukáš Jelínek | přečteno 17401×
Při vytváření instancí objektů (a samozřejmě při používání statických metod třídy) musí být příslušná třída přítomna v JVM - musí být zavedena. Jak jsme si již dříve řekli, nezavede se sama od sebe, udělá to tzv. zavaděč tříd (classloader), ať už implicitně nebo explicitně.
Zavaděčů tříd můžeme mít v systému libovolný počet (a používat je podle potřeby), jeden z nich má však výsadní postavení. Je to tzv. prvotní zavaděč, sloužící k zavedení základních systémových tříd. Nemá svoji javovskou reprezentaci, jedná se o nativní kód a jeho chování je platformově závislé.
Zavádění tříd pracuje na bázi delegování - jeden zavaděč tedy může nechat zavést nějakou třídu jiný zavaděč (nacházející se výše v hierarchii). Takto se může zavedení třídy postupně přenést až k prvotnímu zavaděči.
Každá situace má svůj výchozí zavaděč. Pro aplikace je to URLClassLoader
,
pro applety AppletClassLoader
(je potomkem URLClassLoader
, ale jedná se
o soukromou třídu v balíku sun.applet
, takže nelze běžně používat)
a při zavedení kvůli odkazu z již zavedené třídy se použije ten zavaděč, kterým
se zavedla příslušná třída.
Přestože si můžeme vytvořit svůj vlastní zavaděč a používat ho, v drtivé
většině si vystačíme s tím, co už je ve standardní knihovně - tedy se třídou
URLClassLoader
. Tento zavaděč má totiž vše potřebné k tomu, abychom mohli
zavést neznámou třídu tak, aby nemohla udělat v systému paseku.
Potřebujeme-li při zavádění použít jiný zavaděč, jednoduše ho specifikujeme
v metodě Class.forName()
. Při tomto volání lze rovněž určit, zda se má třída
hned inicializovat (což mj. znamená, že se provede kód v jejím statickém
inicializátoru) - pokud by inicializována nebyla, došlo by k tomu až při
požadavku na vytvoření první instance. Příklad uvedu později.
Existuje ještě jiná, nízkoúrovňovější cesta - zavolání metody loadClass()
příslušného zavaděče. Zde už se ale nepracuje s běžnými názvy tříd, nýbrž
s tzv. binárními názvy, které už známe odjinud. Stačí se podívat, jaké soubory
kompilátor vytvoří pro vnořené třídy (ať už pojmenované nebo anonymní). Právě
tyto názvy souborů odpovídají binárním názvům tříd, samozřejmě bez specifikace
balíku (která je tvořena adresářovou hierarchií).
Použijeme-li k zavedení třídy zavaděč URLClassLoader
, můžeme (pomocí nastavení
bezpečnostní politiky) určit, jaká opravnění budou mít třídy zavedené
z určitého zdroje. Lze tak definovat důvěryhodné zdroje a ty, které naši
důvěru nemají - nezáleží na tom, zda se jedná o lokální zdroje (adresář, JAR
soubor) nebo vzdálené. Jak již všichni vědí z minulé kapitoly, zdroj je
představován třídou CodeSource
a kromě URL může obsahovat i certifikáty.
Nastavení bezpečnostní politiky je klíčová záležitost. Jsou-li aktivní bezpečnostní omezení, kód smí provádět jen ty operace, které mu bezpečnostní politika povolí - výjimkou je pouze čtení souborů ze zdroje, odkud byl kód získán. Než se k nastavování politiky dostaneme, podívejme se nejprve na příklad, jak se takové bezpečné zavádění tříd provádí:
try { URL urls[] = { new URL("http://www.linuxsoft.cz/") }; URLClassLoader ucl = URLClassLoader.newInstance(urls); Class c = Class.forName("MyClass", true, ucl); ... } catch (Exception e) { ... }
Pokud byste chtěli uvedený příklad vyzkoušet, nahraďte prosím URL nějakou vhodnější hodnotou (nejlépe místem, kam uložíte své třídy), aby nebyl server Linuxsoftu zbytečně "olizován" vaším classloaderem.
Bezpečnostní politika je v Javě představována abstraktní třídou java.security.Policy
.
Instancí této třídy může být libovolný počet, aktivní je však vždy pouze
jediná. Nastavení lze za běhu aplikace změnit, ale samozřejmě jen tehdy,
má-li k tomu kód potřebné oprávnění. Objekt má svoje úložiště nastavení
(konfigurace), např. v souboru na disku - změnami v tomto nastavení měníme
bezpečnostní politiku. Pokud ke změně dojde za běhu aplikace, změny se
projeví až v okamžiku, kdy je nastavení aktualizováno metodou refresh()
.
Výchozí nastavení politiky je uloženo v souborech, jejichž umístění se
určuje v konfiguraci bezpečnosti Javy (soubor java.security
v adresáři
/lib/security
pod instalačním adresářem Javy). Typicky se používají dva
soubory - jeden pro úroveň systému (java.policy
v témže adresáři), a druhý
pro uživatelská nastavení (.java.policy
v domovském adresáři). V konfiguračním
souboru java.security
lze nastavit ještě mnoho dalších věcí - doporučuji do
něj nahlédnout, jsou tam velice srozumitelné komentáře k jednotlivým položkám.
Object Policy
funguje tak, že pomocí dvou metod getPermissions()
vrací kolekce
povolení, které tato politika obsahuje. Jedna z těchto metod poskytuje globální
povolení pro určitou doménu (ProtectionDomain
), druhá pro zdroj kódu
(CodeSource
). Dále je tu ještě metoda implies()
, která zjišťuje, zda tato
politika poskytuje určité povolení pro danou bezpečnostní doménu.
I když politiku lze uložit obecně jakkoliv, nejčastěji se používá soubor, proto se na něj nyní podíváme. Platí zde zásadní pravidlo, že implicitně není povoleno nic a my tedy musíme zvolit, co všechno povolíme. Syntaxe souboru je poněkud složitější, podíváme se tedy jen na základní věci - zájemci o hlubší proniknutí do této problematiky nechť nahlédnou do bezpečnostní příručky Javy.
První příklad ukáže, jak povolit úplně všechno všem. Důrazně varuji před aplikací v praxi, jen chci ukázat, jak by se to udělalo:
grant { permission java.security.AllPermission; };
Třída AllPermission
, jak známo, implikuje všechna povolení, proto vložení
tohoto pravidla povolí všechny operace. Protože není uvedeno, koho se to týká,
uplatní se pravidlo globálně. Tohle je ale pravým opakem stavu, který chceme
dosáhnout - tedy aby měl kód méně práv. Podívejme se tedy na další příklad:
grant codeBase "file:/home/user/trusted/*" { permission java.security.AllPermission; }; grant { permission java.io.FilePermission "read","/-"; permission java.io.FilePermission "read,write","/tmp/*"; };
Těmito dvěma pravidly říkáme, že kód pocházející z adresáře
/home/user/trusted
má povoleno všechno, kdežto všechen ostatní kód smí číst
v celém filesystému a zapisovat pouze do adresáře /tmp
. Nemusí se jednat
o lokální kód - můžeme specifikovat i pravidla pro kód odjinud. Podobně
i parametry nemusíme psát "natvrdo", ale lze používat vlastnosti systému:
grant codeBase "http://www.linuxsoft.cz/*" { permission java.io.FilePermission "read,write,execute,delete",\ "${user.home}/-"; permission java.lang.RuntimePermission "queuePrintJob"; };
Uvedené pravidlo se vztahuje na kód pocházející ze serveru Linuxsoft. Zajišťuje povolení provádět veškeré souborové operace v domovském adresáři a jeho podadresářích, a povolení poslat úlohu do tiskové fronty (může to vypadat jako malichernost, ale komu nějaký vtipálek nechá vyplýtvat drahou inkoustovou náplň, nebude považovat takové omezení za zbytečnost).
Uvedená pravidla mají jedno společné - rozlišují kód jen podle zdroje, a již nehodnotí jeho vlastní důvěryhodnost. To není příliš bezpečné, ale vrátíme se k tomu až poté, co zběžně projdeme problematikou podepisování kódu.
Všechna bezpečnostní pravidla jsou sice hezká, ale zatím se nemohla nijak projevit. Nemáme je totiž aktivována. Pokud je chceme uplatnit, je potřeba spouštět Javu trochu jinak, než jak jsme byli zvyklí. Vypadá to zhruba takto:
java -Djava.security.manager Aplikace
Toto spuštění zavede výchozího security managera, a aktivuje tím i definovanou bezpečnostní politiku. Můžete si to vyzkoušet na některé běžné aplikaci - uvidíte, co všechno nebude fungovat (kvůli tomu, že množina hlídaných operací je opravdu velká). Kromě výchozích souborů pro politiku můžete nějaký specifikovat i při spuštění. Pravidla v něm se připojí k těm definovaným ve výchozích souborech (můžeme tak udělit nějaké aplikaci větší práva), anebo je úplně nahradí. Třeba takto:
java -Djava.security.manager -Djava.security.policy=./java.security Aplikace java -Djava.security.manager -Djava.security.policy==./java.security Aplikace
Oba příkazy se liší pouze tím, že v prvním případě se politika přidává, kdežto ve druhém nahrazuje.
Zejména u kódu stahovaného ze vzdáleného serveru je vždy problém s tím, že nám může někdo podvrhnout nebezpečný kód, a to i v případech, kdy server sám patří důvěryhodné osobě. Může být však napaden útočníkem, může být též obětí pharmingu (podvržení DNS odpovědi), čímž se snadno do systému zavleče lecjaká havěť. Proto je žádoucí, aby byla bezpečnost zajištěna lepšími mechanismy - kam patří i podepisování kódu.
Když je kód podepsaný a máme veřejný certifikát pro kontrolu podpisu, lze snadno zjistit, zda je podpis platný a zda do kódu někdo nezasahoval. Základní vlastnosti jsou tu stejné jako u jiného použití elektronického podpisu, zaměřme se tedy na to, jak to použít při zajištění bezpečného prostředí. V Javě je tato oblast dost široce pojata, ale nám teď stačí jen malá podmnožina.
Již dříve jsem se zmínil o tom, že object CodeSource
obsahuje nejen samotný
zdroj, ale i certifikáty. Má to ten význam, že si v souboru konfigurace
politiky nastavíme, že určitá povolení se budou poskytovat pouze kódu
podepsanému určitou osobou. Viz příklad:
grant signedBy "TrustedPerson" { permission java.security.AllPermission; }; grant codeBase "file:/-" signedby "osoba1,osoba2,osoba3" { permission java.io.FilePermission "read,write","/-"; };
První pravidlo říká, že kód podepsaný osobou (přesněji řečeno aliasem)
nazvanou TrustedPerson
bude mít
povoleno úplně všechno. Druhé potom, že kód podepsaný uvedenými osobami, který
pochází z místního filesystému, bude mít plný přístup k souborům.
Nyní samozřejmě vyvstává otázka, kde vezmeme certifikáty pro ověření podpisu. Java používá tzv. úložiště klíčů a certifikátů (keystore), kam se tato data ukládají - umístění je určeno v bezpečnostním konfiguračním souboru. Certifikáty samozřejmě musíme získat bezpečnou cestou - to se netýká těch, které jsou podepsány důvěryhodnou certifikační autoritou, jejíž certifikát máme k dispozici.
S certifikáty a klíči lze manipulovat dvěma způsoby. Prvním je konzolový
program keytool
. Tím lze provádět nejrůznější operace nad úložištěm -
importovat a exportovat certifikáty a klíče, vytvářet páry klíč-certifikát,
odstraňovat položky z úložiště apod. Zde je několik příkladů:
keytool -import -alias TrustedPerson -file TrustedPerson.cer keytool -export -alias osoba -file osoba.cer keytool -list keytool -selfcert -alias lukas -keypass nejakeheslo\ -dname "cn=Lukas Jelinek, ou=, o=Linuxsoft, c=CZ"
První příkaz importuje uvedený certifikát do úložiště (pokud úložiště neexistuje, vytvoří ho) a přiřadí mu alias. Druhý řádek naopak certifikát exportuje. Třetí potom vypíše informace o všech uložených certifikátech. A konečně poslední vytváří certifikát podepsaný sám sebou - použije k tomu klíč uložený pod uvedeným aliasem.
Druhou možností je použít grafický program policytool
. Je už poměrně obstarožní,
ale pro jednoduchou správu úložiště, a také pro práci s pravidly bezpečnostní
politiky ho lze s výhodou použít.
Poslední věcí, na kterou se ještě podíváme, je podepisování kódu. Pro
distribuci takového kódu je samozřejmě výhodné, máme-li klíč/certifikát
podepsané důvěryhodnou certifikační autoritou, ale k podepisování jako takovému
to není třeba. Použijeme k tomu program jarsigner
, který je opět standardní
součástí balíku JDK. Lze podepisovat dokonce i přímo v aplikaci, ale to je
poněkud složitější činnost.
jarsigner
lze použít jak k podepsání JAR archivu, tak ke kontrole podpisu,
máme-li příslušný certifikát. Zde jsou dva příklady:
jarsigner -storepass heslo_uloziste -keypass heslo_klice balik.jar lukas jarsigner -verify balik.jar
První příkaz podepíše archiv balik.jar
pomocí klíče pro alias lukas
. Je
k tomu potřeba jak heslo úložiště, tak heslo soukromého klíče. Lze samozřejmě
balík podepsat i vícekrát. Druhý příkaz pak podpis balíku ověřuje. Uvedené
příklady pochopitelně pokrývají jen nepatrnou část možností jak uvedených
programů, tak oblasti podepisování jako takové. Dokumentace Javy se tím
zabývá velmi široce, ale k dobrému pochopení jsou nezbytné obecné znalosti
problematiky elektronického podepisování.
Sice jsem měl původně poněkud jiné plány, ale na základě četných žádostí čtenářů bude všechno jinak. Příští díl a několik dalších věnuji grafice a grafickým uživatelským rozhraním. Doufám, že se kromě jiného podaří i vyvrátit různé mýty, které se okolo této javovské oblasti vynořují. Bylo by totiž škoda, aby by byla Java zbytečně odmítána, přestože toho má tolik co nabídnout.