2012. október 28., vasárnap

SSL autentikáció kliens oldali azonosítással

Az előző cikkben bemutatott HTTP autentikáció előnye, hogy egyszerű és nem igényel sem sok erőforrást, sem bonyolult konfigurációt. Amiről most írok, az ennél bonyolultabb, viszont cserébe sokkal biztonságosabb és paradox módon egyszerűbb a használata, mint az előzőnek. Hogy hogyan? Lássuk..

A módszer lényege, hogy a webszerver és a böngésző között az adatkapcsolat és az adatok is titkosított csatornán közlekednek, ezért "lehallgatásával" csak a titkosított adatfolyamhoz lehet hozzájutni, nem a tényleges adatokhoz. Ahhoz, hogy a kapcsolódó felek azonosítsák egymást, szükség van egy kulcspárra, Mikor egy https-el kezdődő webcímet nézünk meg, tulajdonképpen mindig ezt használjuk, de úgy, hogy a szervert nem érdekli, ki vagy. A böngésző készít egy kulcsot, azt elküldi a szervernek, a szerver is készít egyet, visszaküldi, és a továbbiakban ezzel kommunikálnak, a külvilág kizárásával. A szerver úgynevezett tanúsítványát, vagyis hogy te tényleg attól kaptad a kulcsot, akinek elküldted, egy harmadik fél, a tanúsítvány kibocsátója hitelesíti. Ezeknek a cégeknek a listája benne van a böngészőben, így biztosítva, hogy tényleg az amazon.com-nak vagy az otp-nek fizettük be a fél fizetésünket, nem valami kamu oldalnak. Mi is hasonlóképpen fogjuk beállítani a dolgot, azzal a csavarral, amit kliens oldali hitelesítésnek hívnak, vagyis nem csak mi azonosítjuk a szervert, hanem nekünk is azonosítanunk kell magunkat a szerver felé, különben a kapcsolat nem jön létre. Ez persze azt is jelenti, hogy hiába tudjuk a szerver címét, ha nincs meg a kulcsunk, nem tudunk csatlakozni hozzá. Pont ez a célunk. :) Az éberebbek most biztosan megkérdezik, hogy rendben, de honnan lesz nekünk harmadik fél által aláírt tanúsítványunk, csak nem kell megvennünk? Nyugalom, nem kell, ugyanis a tanúsítványt mi is legenerálhatjuk a saját gépünkön, és aláírhatjuk vele a kulcspárunkat minden további gond nélkül. Viszont így természetesen a böngészők sápítozni fognak, hogy nem jó az aláírás a tanúsítványunkon, blablabla, ezt sajnos el kell fogadnunk, vagy ha rendelkezünk ilyen tanúsítvánnyal, az még jobb.

Többféle megoldás van a fenti fájlok elkészítésére, ha rákeresünk találunk számtalan leírást. Kipróbáltam többet én is, olyanokat amik egyszerűnek tűnnek, amik bonyolultabbak, mindenfélét, aztán a mellet döntöttem amit itt le fogok írni. Ha valakinek más leírás alapján megy, azzal sincs semmi baj. Én azt a módszert fogom használni, hogy készítek egy konfigurációs fájlt, amiben sok jellemzőt meghatározok, így a parancssorban kevesebb a munka. Ezt a fájlt nevezzük openssl.cnf-nek, sima szövegfájl, lássuk mi van benne:
#
# OpenSSL configuration file. 
# 

# Establish working directory. 
dir = . 

[ req ] 
default_bits = 2048 # Size of keys 
default_keyfile = raspibox.key # name of generated keys 
default_md = md5 # message digest algorithm 
string_mask = nombstr # permitted characters 
distinguished_name = req_distinguished_name 

[ req_distinguished_name ] 
# Variable name   Prompt string 
#----------------------   ---------------------------------- 
0.organizationName = Organization Name (company) 
organizationalUnitName = Organizational Unit Name (department, division) 
emailAddress = Email Address 
emailAddress_max = 40 
localityName = Locality Name (city, district) 
stateOrProvinceName = State or Province Name (full name) 
countryName = Country Name (2 letter code) 
countryName_min = 2 
countryName_max = 2 
commonName = Common Name (hostname, IP, or your name) 
commonName_max = 64 

# Default values for the above, for consistency and less typing. 
# Variable name   Value 
#------------------------------   ------------------------------ 
0.organizationName_default = Nothing inc. 
localityName_default = Metropolis 
stateOrProvinceName_default = New York 
countryName_default = US 
commonName_default = raspibox.no-ip.org

[ v3_ca ] 
basicConstraints = CA:TRUE 
subjectKeyIdentifier = hash 

authorityKeyIdentifier = keyid:always,issuer:always 

[ ca ] 
default_ca = CA_default 

[ CA_default ] 
serial = $dir/serial 
database = $dir/index.txt 
new_certs_dir = $dir 
certificate = $dir/ca.crt 
private_key = $dir/ca.key 
default_days = 3650 
default_md = md5 
preserve = no 
email_in_dn = no 
nameopt = default_ca 
certopt = default_ca 
policy = policy_match 


[ policy_match ] 
countryName = match 
stateOrProvinceName = match 
organizationName = match 
organizationalUnitName = optional 
commonName = supplied 
emailAddress = optional 

Lássuk pár sor magyarázatát. A req szekció default_bits értéke a választott bithossz, alatta a kulcsfájl neve. A commonName_max alatti 5 beállítás a kevesebb (el)gépelést hivatott elkerülni, ugyanis az itt beállított értékeket fogja alapértelmezésnek használni az openssl, amikor kitölteti velünk a tanúsítványt, ezek szögletes zárójelben fognak megjelenni az egyes kérdéseknél, így elég csak az entert csapkodni. A fent említett saját aláírás jellemzőit a CA_default szekcióban állítjuk be, ott a kulcs és a tanúsítványfájl neve (certificate , private_key), a lejárat ideje napokban (default_days) , jelen esetben 10 év. A serial és a database szintén fájlnevek, ezeket egyszerű echo-val, touch-al fogjuk létrehozni, nem sok minden kerül bele, és igazi jelentősége is csak akkor lenne, ha sok tanúsíványt/aláírást készítenénk. Igazából, ennek a konfigos megoldásnak is az lenne az értelme, de így könnyebben átláthatóak a beállítások. (nekem) Ha megvan a konfigfájl, nézzük mit kell vele kezdeni. Először, amint az előbb említettem, hozzuk létre a serial-t tartalmazó fájlt, és írjuk bele hogy "01", illetve egy üres fájlt, amit adatbázisnak fog használni, a már létrehozott aláírásokhoz:
echo '01' > serial
touch index.txt
Most generáljuk le a saját tanúsítvány-kibocsátónkat:
openssl req -new -x509 -extensions v3_ca -keyout ca.key -out ca.crt -days 3650 -config openssl.cnf
Így létrejön a kibocsátó tanúsítvány és a kulcsfájlja, ezzel fogjuk majd aláírni a tanúsítványunkat.
Most készítsük el az aláírás-kérő fájlt, ezzel a lépéssel létrejön a privát kulcsunk is.
openssl req -new -nodes -out req.pem -config openssl.cnf
Most már nincs más dolgunk, írjuk alá:
openssl ca -out raspibox.crt -config openssl.cnf -infiles req.pem
Meg fogja kérdezni, hogy biztos-e, kiírja az adatokat, majd megint, és aláírja. Még egy apróság, az így létrejött raspibox.crt-t a böngészők nem tudják használni, ők a PKCS #12 formát szeretik, konvertáljunk hát egy ilyet:
openssl pkcs12 -export -clcerts -in raspibox.crt -inkey raspibox.key -out raspibox.p12
Itt kérdez egy jelszót, amit azért hasznos beállítani, hogy ha valahol mégis elszórjuk ezt a fájlt, ne lehessen használni csak ezzel a jelszóval. Az így létrejött fájl már be lehet importálni a böngészőkbe (ott fogja kérdezni a jelszót, nehogy elfelejtsük), operációs rendszerbe (win és osx használ ilyen közös tárolót, pl). Vannak olyan programok, amik meg az úgynevezett PEM fájlokat szeretik, ezek egyszerű kombinációi a két fájlnak, csináljunk ilyet is azért, hátha kell:
openssl pkcs12 -in raspibox.p12 -out raspibox.pem -clcerts
Most már csak rá kellene venni az nginx-et, hogy titkosítsa a kapcsolatot, a fenti fájlok segítségével/azok beállításával. Ehhez meg kell neki mutatnunk az tanúsítványt, a tanúsítvány kulcsát, a kibocsátó tanúsítványát, illetve (nyilván), be kell kapcsolni az ssl-módot, és beállítani a portot 443-ra, ugyanis a böngészők ott keresik a https-t. Valamint állítsuk be hogy a kapcsolat csak akkor jöjjön létre, ha a kliens is méltóztatja azonosítani magát, az előbb készített p12 fájllal. Nyissuk meg a konfigfájlt, és tegyük is meg ezeket:
sudo vim /etc/nginx/nginx.conf
Keressük meg a szerver beállítását, és tegyük bele a következő sorokat:
        listen       443;    # port átállítása
        ssl on;                 # modul bekapcsolása

        # fájlok útvonalainak beállítása
        ssl_certificate      /home/raspi/sslcert/raspibox.crt;
        ssl_certificate_key  /home/raspi/sslcert/raspibox.key;
        ssl_client_certificate /home/raspi/sslcert/ca.crt;

        ssl_verify_client on;      #  kliens azonosítás kényszerítése

#  a tanúsítványlánc bejárásának mélysége.
# A defaultja is egy, de nem baj ha megadjuk 

        ssl_verify_depth 1;
Nem felejtetünk el valamit? Mi van a php-vel, hiszen cgi-vel fut. Jogos, neki is meg kell mondani, hogy a kapcsolat hitelesítése sikerült (SUCCESS vagy NONE, az nginx a $ssl_client_verify változóban átadja), illetve egy DN nevű, néhány érték összefűzéséből létrejövő stringet (ezt az nginx megcsinálja, csak a változónevet kell átadni). Ezeket ott adjuk meg, ahol a  többi fastcgi paramétert, a " location ~ \.php$ {" részben, rakjuk a végére valahova mondjuk:
  location ~ \.php$ {
          root   /var/www/rutorrent;
          fastcgi_pass   127.0.0.1:9000;
          fastcgi_index  index.php;
          include fastcgi_params;
          fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
          fastcgi_param  QUERY_STRING     $query_string;
          fastcgi_param  REQUEST_METHOD   $request_method;
          fastcgi_param  CONTENT_TYPE     $content_type;
          fastcgi_param  CONTENT_LENGTH   $content_length;
          fastcgi_intercept_errors        on;
          fastcgi_ignore_client_abort     off;
          fastcgi_connect_timeout 60;
          fastcgi_send_timeout 180;
          fastcgi_read_timeout 180;
          fastcgi_buffer_size 128k;
          fastcgi_buffers 4 256k;
          fastcgi_busy_buffers_size 256k;
          fastcgi_temp_file_write_size 256k;

          # itt a két új paraméter
          fastcgi_param  VERIFIED $ssl_client_verify;
          fastcgi_param  DN $ssl_client_s_dn;
          # itt a két új paraméter

}
Minden megvan, mentsük el a fájlt (előtte azért ellenőrizzük, hogy a lezáró ;-őt ott vannak e a sorok végén, hogy nem töröltünk-e ki nyitó és/vagy záró kapcsos zárójelet, nem írtuk-e el az elérési utakat, szóval, hogy nem rontottuk-e el. Indítsuk újra az nginx-et:
sudo systemctl restart nginx.service
Ha nem írt ki semmit, akkor jó, ha valami "failed"-ről magyaráz, vizsgáljuk ki. :)
Most ellenőrízzük hogy csakugyan a 443-hoz került-e át a rutorrentünk, nyissuk meg a böngészőben a https://<raspi -ip> -t . Előjön a "nem hitelesített oldal", "tanúsítvány megerősítése", szokásos, örömünk határtalan. Értsük meg a kockázatokat, töltsük le és erősítsük meg a tanúsítványt. Ekkor az nginx egy nem túl barátságos üzenetben közli, hogy 400-as hiba, nem küldtél kliens tanúsítványt. Bizony nem. Menjünk hát a beállításokba, keressük meg azt a helyet, ahol tanúsítványokat lehet kezelni, és importáljuk be a raspibox.p12 fájlunkat.


Kérni fogja az import jelszót. Most már beenged az nginx, tudjuk használni a rutorrentet. Chrome-ban, IE-ben máshogy néznek ki a dialogok, de nincs azért nagyon eldugva, meg lehet találni.
A kompatibilitás miatt Windows alatt készültek a screenshotok, így talán ismerősebb. (igen, tudom hogy a grafikus világ sokat veszített hogy nem azzal foglalkozom, de hát ez van)

Nincsenek megjegyzések:

Megjegyzés küldése