May 12, 2014

Koltozes

Mostanaban sok egyeb teendom miatt, teljesen elhanyagoltam a blogot. Egy ujat inditok, ezuttal angol nyelven. A tema leginkabb Php (Symfony2, Laravel) lesz ott is. Hamarosan erkezik az elso poszt: code-chronicle

Oct 4, 2012

Symfony2 - saját jelszó kódoló (encoder) készítése

A napokban kezdtem el fejleszteni egy hobbi projektet Symfony2-ben és az ezzel kapcsolatos tapasztalataimat osztom majd meg a mostani - és lesznek majd később is Symfony2-vel kapcsolatos posztok - bejegyzésben.

Tehát Symfony2-ben van egy nagyon ügyes csomagunk (bundle - Symfony2-ben minden csomagokba van rendezve, fel lehet fogni egyfajta plugin-ként, amiben a forrástól kezdve a hozzá tartozó erőforrások [css, js...] is megtalálhatók) amit Security néven érhetünk el. Ez a könyvtár biztosítja számunkra (természetesen nem muszáj ezt használni) az authentikációt, authorizációt és ezáltal képes a teljes bejelentkezést lebonyolítani, némi konfiguráció árán.

A komponens konfigurációjában lehetőség van az encoder tulajdonságainak beállítására, amellyel megadhatjuk milyen fajta egyirányú kódolást (hash) szeretnénk alkalmazni (plusz még, hogy hány iteráción keresztül álljon elő ez a hash és hogy base64 formában szeretnénk-e tárolni) jelszavaink tárolásakor.

Alapértelmezett esetben elég szép számmal kapunk hash algoritmusokat, amelyeket használhatunk,. Viszont mi szeretnénk írni egy saját encoder-t, nem elégszünk meg az általánosan használt jelszó kódolási eljárásokkal (pl. md5, sha1).
Hogy miért nem? Az említett algoritmusok túlságosan gyorsak. Amennyiben valaki megszerez egy adatbázist és rendelkezik megfelelő szivárvány táblával, mai GPU-kal gyorsan sikerrel járhat a jelszavak törésében. Persze még mindig jobb az md5 vagy az sha1 használata, mint a plaintext.
Mivel javíthatunk felhasználóink jelszavainak kódolásán?
  • Megköveteljük, hogy a felhasználó milyen formájú, hosszúságú jelszavat adhat meg (pl. szükséges kis- és nagybetű, szám illetve, hogy x hosszúságú legyen. Lehet extrémebben is, mondjuk hogy ismert, egyszerű szavakat kiszűrünk, azokat nem tartalmazhatja a bevitt adat.)
  • Sózással. Az egyszerű jelszót kicsit megfűszerezzük egy hosszabb, véletlenszerű karakterekből álló lánccal.
  •  Kiegészítő eljárás bevonása a kódolásba: például a key stretching, azaz a kulcs nyújtása.
  • Valamilyen erősebb kódolású, nem feltétlenül gyors (egyirányú kódolási) algoritmust használunk.
Az első ponttal az a probléma, hogy a felhasználó "életét" nehezítjük meg. A sózás viszont csak abban az esetben hatékony, ha minden látogatóhoz külön, egyedi sót alkalmazunk. Amennyiben így teszünk, nem lehetetlenítjük el, de legalább megnehezítjük a támadó dolgát. Ebben az esetben a krekkernek minden egyes felhasználóhoz külön szivárvány táblát kell generálnia (tegyük fel, hogy hozzáférést szerzett az adatbázishoz, azzal együtt ugyebár a sóhoz is).

Kiegészítő eljárásként a kulcs ún. nyújtását említettem meg, amely technika arra hivatott, hogy megerősítse az egyébként gyenge kódolást, hogy hatékonyabb legyen egy esetleges brute force ellen (egyébként a Symfony2 is ezt a technikát használja). Az eljárás után egy megerősített kulcsot kapunk. Az funkció lényege tehát, hogy több ideig tart ellenőrizni egy bizonyos megfelelést a hash-re. Legegyszerűbben az algoritmust leíró pszeudo kód alapján érthető meg:

key = ""
for 1 to 65536 do
    key = hash(key + password + salt)

Az utolsó pont szintén a kulcs nyújtását használja fel alapgondolatként, itt konkrétan a bcrypt, nevű függvényre gondoltam. A kódolással kapcsolatos problémáink gyökere az algoritmus sebességén alapul. Amennyiben nekünk rövid ideig fog tartani az adott hash előállítása, akkor a jelszavainkat törő személynek is, ezért próbáljunk inkább lassabb funkciót használni, hogy még tovább tartson az esetleges szivárvány táblák előállítása. Ez irányú erőfeszítésünkre (lassítás) nyújt segítséget az előbb említett bcrypt egy cost (vagy ha úgy tetszik work factor) paraméterrel. Ő egy két számjegyű érték, amely a [04-31] intervallumban kell, hogy szerepeljen. A kiválasztott szám az iterációs lépések kettes alapú logaritmusa. Adjunk meg cost paraméternek a 12-t, ekkor az iterációk száma (key stretching - előző pszeudo kód) 212 lesz, azaz 4096 (log24096=12). Egyik kedvenc szkript nyelvünkön (a másik ugyebár a Python :]) a crypt funkcióval használható.

Az elméleti kiruccanás után térjünk át a Symfony2 specifikus részre. Nos, alap esetben a következőképpen fest a konfigurációnk (app/config/security.yml), ha nem kívánjuk kiegészíteni a kódolási mechanizmusunkat például a bcrypt-el:

encoders:
    Symfony\Component\Security\Core\User\User:
        algorithm: sha1
        iterations: 128
        encode_as_base64: false

A konfig magáért beszél, megadjuk a kívánt algoritmust, az iterációk számát és hogy base64 kódolásban szeretnénk-e letárolni az eredményt. Ahhoz, hogy saját encoder-t készítsünk, implementálnunk kell a Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface felületet:

namespace B3ha\UserBundle\Service;

use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;

class BCryptEncoder implements PasswordEncoderInterface
{
    /**
     * @var integer Must be in range 4-31
     */
    protected $cost;
 
    /**
     *
     * @param integer $cost
     */
    public function __construct($cost)
    {
        $cost = (int)$cost;
        if ($cost < 4 || 31 < $cost) {
            throw new \Symfony\Component\Config\Definition\Exception\Exception(
                'Cost parameter must be in range 4-31');
        }
        $this->cost = sprintf('%02d', $cost);
    }
 
    public function encodePassword($raw, $salt)
    {
        return crypt($raw, "\$2a\${$this->cost}\${$salt}");
    }

    public function isPasswordValid($encoded, $raw, $salt)
    {
        return $encoded === $this->encodePassword($raw, $salt);
    }
}

A fenti kódhoz nem is fűznék kommentárt, elég kicsi ahhoz, hogy érthető legyen. Ha készen vagyunk az implementálással, akkor a következő lépésként beköthetjük a saját kódolónkat:

encoders:
    B3ha\UserBundle\Entity\User:
        id: b3ha.user.bcrypt_encoder

Érdemes service-ként (ActualBundle/Resources/config/services.yml, majd a fő konfig fájlban ezt importálni) megvalósítani (akár a ServiceContainer-ről is lehet későbbiekben poszt) az encoder-t, ezt a lenti kód mutatja:

parameters:
    # Cost parameter must be in range 04-31
    b3ha.user.bcrypt_encoder.cost: 12

services:
    b3ha.user.bcrypt_encoder:
        class: B3ha\UserBundle\Service\BCryptEncoder
        arguments: [%b3ha.user.bcrypt_encoder.cost%]

Nem tértem ki minden részletre, mert ez a poszt inkább az elméleti részről és a Symfony2-ben már kicsit   jártasabb kódereknek szólt. Részletes leírásokat természetesen a symfony.com oldalon találhattok, ha kérdésetek, kiegészítésetek vagy javításotok lenne írjatok bátran.

Feb 21, 2012

Imap "érdekességek" :/

Bő két hónapja esett meg az a szerencsétlenség, hogy volt alkalmam összefutni a php imap libbel. Ami főleg azért volt számomra szerencsétlen, mert előjött pár olyan idegőrlő "érdekesség", amely nem tett túl jót a neurális hálózatomnak.
A doksi írást senki sem szereti, ez tény. Viszont aki nem, vagy csak részben ír dokumentációt másokkal szúr ki, ez esetben velem (és gondolom még sok más fejlesztővel is).
Az imap-al kapcsolatos alapvető felhasználásra nem térek ki, ezek a pontok azoknak lehetnek hasznosak, akik már valamennyire működő kódot hoztak össze, vagy ezt elolvasva a jövőben talán visszaemlékeznek az egyes részekre.
Lássunk tehát néhány hasznos információt a php-s imap-al kapcsolatban, amikkel volt "szerencsém" találkozni:

  • Postafiók megnyitás (ez jól le van dokumentálva, de az egyik vázlatpont miatt nem árt, ha látjuk)
    imap_open(string $mailbox , string $username , string $password [, int $options = NIL...);
    szerver specifikáció:
    {host:port/flag}
    (hasznos option: \OP_HALOPEN -> csak kapcsolatot nyit, de mappát nem)
  • Függvények hibát dobnak a @ operátor ellenére is
    egyszer bejelentett bug
    másszor bejelentett bug
  •  A lib szereti cache-elni a dolgokat, ezért: imap_gc(...); !
  • imap_append options paraméternek az imap_clearflag_full függvénynél az options paraméter értékei is megadhatóak (The flags which you can unset are "\\Seen", "\\Answered", "\\Flagged", "\\Deleted", and "\\Draft" (as defined by » RFC2060)). Például egy levelet olvasottként akarunk az egyik mappába helyezni.
  • Karakterkódolás: Nos, itt nem sikerült tökéletes megoldást előállítanom, így nincs is mit publikálnom sajnos (egy kisebb szkriptről volt szó, amely egyszer volt használatos és nem volt elvárt pl a magyar ékezetes betűk használata). A lényeg, hogy az ékezetes betűkkel rengeteget lehet szívni. Nem folytam nagyon bele, de valszeg rosszak a saját kódolási függvényei: imap_utf7_encode imap_utf7_decode
  • A végére még egy gyöngyszem, az imap_mail_move. Itt az a csodálatos, hogy a szerver spec (első pontban tárgyalt) nem olyan formában kell, hogy szerepeljen, mint ahogy (szinte - pl a copynál is a move-hoz hasonló, viszont append-nél már nem) minden más függvénynél (azaz {host:port/flag}/Folder), hanem neki csakis a Folder rész szükséges.
  • Ráadásként pedig az imap_getmailboxes visszatérési értéke, amely hiányosan van dokumentálva, de szerencsére az egyik kommentelő előkotorta a C forrásból a maradékot :)
Kb ezek voltak amikkel találkoztam és emlékeztem is rájuk. Lehetséges persze, hogy más függvényeknél is előjehetnek hasonlók. A cél, a figyelem felhívása volt a labilis pontokra, azok számára akiknek esetleg ezzel a könyvtárral kell dolgozniuk a jövőben.

Oct 14, 2011

SOAP Messages with Attachments

A héten futottam bele egy feladatba [problémába :)], amelynél webszolgáltatáson keresztül, nem csupán xml adatstruktúrát, de csatolt állományokat is fel kellett dolgoznom.
Egy report szerverről (java alapú) volt szó, amelynek ha elküldünk egy megfelelően felépített xml-t - mint paramétert - SOAP-on keresztül, akkor az annak megfelelő dokumentumot legenerálja, majd úgyszintén webszolgáltatáson keresztül szolgáltatja vissza. Ki lehet találni, hogy az elküldésnél még minden rendben volt, viszont amikor a válasz üzenet visszaérkezett, akkor jelentkezett a - igazából várt  - probléma.
Tegyük fel, hogy  pdf kiterjesztésben kérem el a dokumentumot. Ilyenkor a válasz üzenet multipart/related tartalom típussal (Content-Type) fog érkezni. Röviden ez azt jelenti (részletesebben itt), hogy bár egy üzenet érkezik válaszként, az több különálló részre lesz osztva. A Content-Type fejlécben megadott boundary értékével lesznek az egyes részek elválasztva, amelyek külön-külön fejlécekkel és egyedi azonosítóval (Content-Id) rendelkeznek. A boundary információ mellett - related esetben - egy start attribútumot is találunk, értéke egy Content-Id-ra való hivatkozás. Azaz a start jelöli ki - esetünkben - a SOAP envelope-ot. Az, hogy az envelope-on belül milyen hivatkozások vannak a csatolt állományokra, minket most nem érdekel, csak a különböző részeket szeretnénk feldolgozni. Amikor éppen nem az envelope-ot dolgozzuk fel, akkor figyelni kell a Content-Transfer-Encoding fejlécet, mert ez lehet bináris, de akár base64 kódolású is. A leírtak nagyrészt (A kód közel véglegesnek tekinthető, de még vár rá néhány teszteset) lefedik a SOAP Messages with Attachments fogalmát.

Nézzük egy (kölcsönzött) példát a kapott válaszüzenetre:
MIME-Version: 1.0
Content-Type: Multipart/Related; boundary=MIME_boundary; type=text/xml;
        start="<http://claiming-it.com/claim061400a.xml>"
Content-Description: This is the optional message description.

--MIME_boundary
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: 8bit
Content-ID: <http://claiming-it.com/claim061400a.xml>
Content-Location: http://claiming-it.com/claim061400a.xml


<?xml version='1.0' ?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
...
<theSignedForm href="http://claiming-it.com/claim061400a.tiff"/>
...
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

--MIME_boundary
Content-Type: image/tiff
Content-Transfer-Encoding: binary
Content-ID: <http://claiming-it.com/claim061400a.tiff>
Content-Location: http://claiming-it.com/claim061400a.tiff

...binary TIFF image...
--MIME_boundary--


Egy ehhez hasonló üzenetet kellene tehát feldolgoznunk, hogy utána az egyes részek kényelmesen lekérhetőek legyenek. A gond viszont az, hogy a natív PHP SoapClient osztály az ilyen típusú üzeneteket nem támogatja, így másfelé kell nézelődnünk.
A problémára két megoldást találtam. Kibővítem a meglévő SoapClient osztályt, vagy egy kész könyvtárat használok (sem NuSOAP-ban, sem ZendFwSoap-ban nem találtam a feature-t [Ha valaki tud wso2-höz hasonló könyvtárat, kommentben kérem ossza meg]). A kész könyvtár a WSO2 Web Service Framework for PHP lenne, amely nagyon sok funkciót tud, köztük az SwA helyett ajánlott MTOM-ot (ez a kettő lényegében megegyezik, csupán az MTOM előrehaladottabb a csatolások belinkelésével).
A kibővítés mellett döntöttem, egyrészt mert a jövőre tekintve is elegendőnek tűnik a majd lentebb látható megoldás, másrészt a kódegység tartása (natív PHP SOAP osztályokat használok az érintett projektben) végett az összes SOAP-al kapcsolatos kódrészt át kellett volna írni, a könytárnak megfelelőre.
Mostmár rátérhetünk a gyakorlati részre, a kódra. Itt nem szeretnék hosszas magyarázatokba bocsátkozni, a lényeg annyi, hogy a SoapClient osztály __doRequest metódusát fogjuk felülírni. Ez az a metódus amely elküldi a kérést, majd visszatér a válasszal. Nekünk akkor kell beavatkoznunk, ha több részes üzenetet fedezünk fel a válaszban, egyébként az eddigi működést támogatjuk. Lekéréskor az összes részt, vagy csupán egyet is elkérhetünk. Egy résznek van fejléc része, amely kulcs-érték párokat tartalmaz és van tartalma, ezenkívül meg tudja mondani magáról, hogy ő maga-e az envelope.


Oct 11, 2011

Zend action controller, függvénybeli paraméterekkel


Rövid szünet után egy Zend Framework-el kapcsolatos poszt következik. Sajnos Zendéknél alapból, nincs lehetőségünk az Action metódusokban paramétereket megdani. Ez nekünk főleg, azért rossz, mert mi szeretnénk az url-ben átadott paramétereket ($_GET) azonnal lekérni a függvény paraméterein keresztül, nem pedig például a $this->getRequest()->getQuery() segítségével. Ezen probléma orvoslására már bizonyára sok megoldás született, de mivel én a Zf-et eddig nagyrészt csak komponensenként használtam főleg CodeIgniter alól, így nekem újdonság volt a dolog.
Alap esetben az url-ben szereplő paraméterek lekérdezésére a következő lehetőségünk van:
$this->_request->getQuery('query_string_parameter_name'); // zf request object
A cél eléréséhez a Zend_Controller_Action osztály dispatch metódusát kell felülírnunk. Azért van erre a függvényre szükségünk, mert ő lesz aki meghívja a megfelelő controller adott action metódusát, amelyet paraméterben megkap (egyszerű string-ként).
A kód megtekintése előtt csupán annyit emelnék még ki, hogy két lehetőségünk van az url-ben paramétereket átadni az adott akciónak:
  • module/controller/action?p1name=p1value&p2name=p2value
  • module/controller/action/p1name/p1value/p2name/p2value
(2. esetben a module, controller és az action kulcsok foglaltak)


Ui.: A fenti megoldás csupán teszt jellegű, éles környezetben nincs használva. Ezen kívül még annyi, hogy validációnál hátrányban lehetünk ilyen jellegű felhasználás esetén (változókban vannak a bemeneti értékek, a könnyebben kezelhető tömb helyett).