Aláírt kommunikáció (signed request)

Az alkalmazások lehetőségeit bemutatva írtam már róla, hogy az alkalmazás kommunikálni tud a külvilággal is, és megemlítettem, hogy lehetőség van ezt a kommunkációt hitelesíteni (ez az aláírt kommunikáció), vagyis bizonyíthatóan igazolni, hogy mely féltől érkezett a kérés. A bejegyzésben arról írok, hogy hogyan működik, mire használható ez a lehetőség.

Aláírt kommunikációra akkor van (leginkább) szükségünk, amikor egy felhasználó nevében saját szolgáltatáshoz fordulunk, és szeretnénk tudni, hogy:

  • valóban az iWiW felől jött-e a HTTP kérés
  • mely felhasználótól jött a kérés
  • mely alkalmazástól jött a kérés

A felhasználói azonosítót ugyan átadhatjuk egy sima GET paraméterben is, továbbá azt is nézhetjük hogy mely IP címek felől érkezett a kérés, ellenben ezek nem sorolhatóak a biztonságos és jó megoldások körébe, mivel nem túl megbízhatóak. Például lehetséges egy adott alkalmazás oldalon, annak nevében kéréseket indítani - ekkor ha az alkalmazás teszi be a felhasználó azonosítóját a paraméterek közé akkor azt könnyen ki lehet cserélni, és más felhasználó nevében is lehet ténykedni (vagy olyan információhoz is hozzá lehet jutni, mely nem tartozik a trükközőre). Az IP címek is változhatnak idővel különösebb értesítés nélkül, az pedig nem jó ha emiatt akármennyi időre is működésképtelen lesz az alkalmazásunk, mert nem fogadja el az új címről a kéréseket.

A biztonságos megoldást az aláírt kérések jelentik, melyek használata nagyon egyszerű: a kérésben elküldött (GET/POST) paramétereket az iWiW OpenSocial motorja kiegészíti további paraméterekkel, többek között egy olyan ellenőrzőösszeggel, mely segítségével ellenőrizhető a kérés forrása. A kiegészítő információk a következők:

  • opensocial_owner_id: az alkalmazás tulajdonosa
  • opensocial_viewer_id: az alkalmazás nézője
  • opensocial_app_id: az alkalmazás azonosítója
  • opensocial_app_url: az alkalmazás XML-jének url-je

Az ellenőrzőösszeget a folyamat az összes paramétert figyelembe véve állítja elő felhasználva egy speciális kulcs értéket is, a végeredmény pedig a az oAuth szabvány aláírás szabályainak megfelelően képződik (az aláírt kéréseknek ennyi, és csak ennyi a köze az oAuthhoz, amiről még lesz szó, és ami amúgy teljesen másra jó). Az ellenőrzőösszeget kell leellenőriznünk a szerver oldalon, mert ez az, amit csak és kizárólag az iWiW fog tudni létrehozni, más “támadó harmadik fél” nem. Ha ez stimmel, akkor biztosak lehetünk benne, hogy az előbb felsorolt paraméterek is hitelesek, megbízhatóak.

Az oAuth szabvány nem definiálja kizárólagosan hogy mely algoritmust kell használni az aláírások készítéséhez, ezt a megvalósításra bízza rá. Az iWiW a következő algoritmusokat támogatja:

  • RSA-SHA1: Egy egy nyílt kulcsú titkosítást használó algoritmus, avagy egy titkos és egy publikus kulcs segítségével történik az aláírás. Ezt az algoritmus az alapértelmezett iWiW kulccsal használható signed request kérések esetén. A titkos kulcs az iWiW-nél van és ezzel készül az ellenőrzőösszeg, míg a publikus kulcs segítségével ellenőrizhető hogy valóban az iWiW-től jött-e a kérés.
  • HMAC-SHA1: Ez egy megosztott rejtett kulcsot használó algoritmus, avagy egy darab kulcs van csak, melyet mind titkosításkor, mind pedig ellenőrzéskor használni kell. Ez az algoritmus a fejlesztő által feltöltött kulcsokkal használható signed request és oAuth kérések esetén.
  • HMAC_SYMMETRIC: ugyanúgy működik, mint a HMAC-SHA1.

Ennyit az elméletről, lássuk hogy hogyan működik mindez a gyakorlatban. Alapértelmezett esetben egy alkalmazásnál az RSA-SHA1 algoritmussal, az iWiW alapértelmezett kulcsaival működik az aláírás. A kérés indítása az alkalmazásunkban:

gadgets.io.makeRequest(
  "http://iwiw.example.com/request.url?param=iWiW",
  function(data){ alert(data.text); },
  {
    METHOD: gadgets.io.MethodType.GET,
    CONTENT_TYPE: gadgets.io.ContentType.TEXT,
    AUTHORIZATION: gadgets.io.AuthorizationType.SIGNED
  }
);

A szerver oldalt PHP-ben valósítottam meg, ehhez egy kész oAuth függvénykönyvtárat használtam, az oAuth.php fájl kellett belőle, ez lett betöltve. Más szerver oldali nyelvet használóknak segítséget nyújthat az oAuth oldalán levő felsorolás, az elv mindenhol ugyanaz, az elterjedtebb programozási nyelvekhez kész kódok érhetőek el.

A PHP kód tehát:

<?
include "oAuth.php";
 
class IWiWSignatureMethod extends OAuthSignatureMethod_RSA_SHA1 {
  protected function fetch_public_cert(&$request) {
    return <<<EOD
-----BEGIN CERTIFICATE-----
MIICRTCCAa4CCQCJTQjR2NRBSTANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJI
VTERMA8GA1UECBMIQnVkYXBlc3QxETAPBgNVBAcTCEJ1ZGFwZXN0MREwDwYDVQQK
EwhJV0lXIEx0ZDENMAsGA1UECxMEaVdpVzEQMA4GA1UEAxMHaXdpdy5odTAeFw0w
ODA5MTgxMDAzMDVaFw0wOTA5MTgxMDAzMDVaMGcxCzAJBgNVBAYTAkhVMREwDwYD
VQQIEwhCdWRhcGVzdDERMA8GA1UEBxMIQnVkYXBlc3QxETAPBgNVBAoTCElXSVcg
THRkMQ0wCwYDVQQLEwRpV2lXMRAwDgYDVQQDEwdpd2l3Lmh1MIGfMA0GCSqGSIb3
DQEBAQUAA4GNADCBiQKBgQCgSymvAxyuISIufaSFEAiP1WpivlIlvIa7eIzpjA7H
JqmGtJzosi6IihqFTwthiriZ09d1J4R6jTeqm/CXKGcmUwTqBULxbZC1Yw6dGJMw
FjacooFvu5iHVAM3OFyG0DsMZWwT/XSexybc1+vPHdwkMSgZOg/FgoivahZlV3Mt
8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACfd/y/nAXGXFShDXzFhsDC43izjIg13
STzhIUzsXVFg+zhik7XghjMsohMF99n6GYdRFIoRZpqMxIYQnnw5i10FIu57U39j
kwo4wtCVsSDq/C5un2kcTPJL+kH6P/8OzsaRRuuCn5N7rjGNtB4LvX/JIIxEfS+/
KR+yjqXrml+O
-----END CERTIFICATE-----
EOD;
  }
}
 
$request = OAuthRequest::from_request(null, null, array_merge($_GET, $_POST));
$signature_method = new IWiWSignatureMethod();
@$signature_valid = $signature_method->check_signature($request, null, null, $_GET["oauth_signature"]);
if ($signature_valid == true) {
  echo 'Hello '.$_GET['param'].'!';
  // echo $_GET['opensocial_owner_id'];
  // echo $_GET['opensocial_viewer_id'];
} else {
  echo 'Sikertelen hitelesítés.';
}
?>

Először definiálunk egy IWiWSignatureMethod osztályt mely a OAuthSignatureMethod_RSA_SHA1 osztály leszármazottja lesz, s amit az ellenőrzőösszeg kiszámításához fogunk felhasználni. Létrehozunk egy OAuthRequest objektumot, ami mind a GET-es, mind pedig a POST-os paramétereket megkapja, példányosítjuk az előbb létrehozott IWiWSignatureMethod osztályt, és meghívjuk a check_signature metódusát, mely le fogja ellenőrizni, hogy az oauth_signature GET paraméterben megfelelő érték jött-e. Ha igen, akkor visszaküldjük a kapott paramétert egy “Hello” előtaggal, ha nem, akkor megmondjuk hogy rossz volt a kérés.

Ha feltöltöttünk már egy kulcsot, akkor az aláírt kérések nem az iWiW alap kulcsát, hanem az általunk feltöltött kulcsot fogják használni, ezekhez HMAC_SHA1 vagy HMAC_SYMMETRIC kódolást választhatunk. Ezek ahogy az előbb is említettem, nem publikus és titkos kulcspárt használnak, hanem egy megosztott rejtett kulcsot. Ez a kulcs “bármilyen” szövegrészlet lehet, persze érdemes egy megfelelően hosszú, véletlenszerűhöz közelítő, nem kitalálható kulcsot választani. A következő tehát rossz példa:

hmac_sha1_pelda

Az előbb használt PHP-s oAuth kódkönyvtár a HMAC-SHA1 kódolást támogatja, ezért én ezt választottam. A kliens oldali kódunk teljesen változatlan, míg a szerver oldali kód a következőképpen alakul:

<?                                                                                                                                                                                 
include "oAuth.php";                                                                                                                                                               
 
$request = OAuthRequest::from_request(null, null, array_merge($_GET, $_POST));                                                                                                   
$consumer = new OAuthConsumer($_GET['oauth_consumer_key'], 'teszt kulacs', NULL);                                                                                                
$signature_method = new OAuthSignatureMethod_HMAC_SHA1();                                                                                                                        
@$signature_valid = $signature_method->check_signature($request, $consumer, null, $_GET["oauth_signature"]);                                                                     
if ($signature_valid == true) {
  echo 'Hello '.$_GET['param'].'!';
  // echo $_GET['opensocial_owner_id'];
  // echo $_GET['opensocial_viewer_id'];
} else {
  echo 'Sikertelen hitelesítés.';
}
?>

Először létrehozunk egy OAuthRequest objektumot, ami mind a GET-es, mind pedig a POST-os paramétereket megkapja, példányosítunk egy OAuthConsumer objektumot és egy oAuthSignatureMethod_HMAC_SHA1 osztályt, és meghívjuk ez utóbbi  check_signature metódusát, mely le fogja ellenőrizni, hogy az oauth_signature GET paraméterben megfelelő érték jött-e. Végül ha igen, akkor ahogy az előbb is, visszaküldjük a kapott paramétert egy “Hello” előtaggal, ha nem, akkor megmondjuk hogy rossz volt a kérés.

A kulcs nevének érdemes egy a kulcs használóját, funkcióját beazonosító karaktersorozatot választani, például “example.com”, “example.com/app1″, de ezek nem kötöttségek, csak javaslatok.

A funkció használatához jellemzően elegendő lehet ennyi ismeret, de célszerű lehet utána olvasni az oAuth-nak, a kriptográfiának általában, a nyílt kulcsú titkosításnak, az RSA eljárásnak és az egyéb kapcsolódó témaköröknek, hogy tudjuk pontosan mit használunk, és hogyan működik.

11 megjegyzés - “Aláírt kommunikáció (signed request)”


  1. 1 andras

    Hello!

    Ugy latom a HMAC-DSA1-et nem nagyon szeretitek :-) Minden pelda itt es is a wikin is RSA. Ez szep es jo, de en nem csak abban szeretnek biztos lenni hogy a keres az iwiw containerbol jott hanem hogy az en alkalmazasom kuldte. A HMAC-DSA1 pedig lehetoseget biztosit arra (mint olvashattuk) hogy sajat (egyedi) kulcsot allitsak ki.

    A problemam teljesen egyszeru: en nem azt a signature-t kapom ugyanazokkal az adatokkal amit kuldtok. Ambar lehet (sot, remelem) hogy *most* :) tevedek, igy aki kedvet erez hozza cafoljon meg kerem szepen.

    A consumer_secret -em (azaz a “titkos kulcs”)
    0xeiHcIib7Uk0Kp1CBLDDNmzzqT8k3hRGrQFpCA1TqA

    A teljes alairt request, ahogy az access logomban latom

    84.2.37.110 - - [07/Jan/2009:11:07:17 +0000] “GET /opensocial/ping?opensocial_owner_id=sandbox.iwiw.hu%3AB9hDSW6tQ8&opensocial_viewer_id=sandbox.iwiw.hu%3AB9hDSW6tQ8&opensocial_app_id=2018662519&opensocial_app_url=http%3A%2F%2Fwgw.collisioncourse.cc%2Fopensocial.xml&oauth_version=1.0&oauth_timestamp=1231330978&oauth_consumer_key=ahWDDmGMkB4amIlKywHGfA&oauth_signature_method=HMAC-SHA1&oauth_nonce=1231330978966564000&oauth_signature=zAbMelOluxldSp5e2o0oDqTxt1c%3D HTTP/1.0″ 401 21

    ebbol a kovetkezoket banyaszom elo:
    base_string, a kovetkezo szerint allitottam ossze: http://wiki.oauth.net/TestCases:
    Ebbol:

    GET http://wgw.collisioncourse.cc/opensocial/ping?opensocial_owner_id=sandbox.iwiw.hu:B9hDSW6tQ8&opensocial_viewer_id=sandbox.iwiw.hu:B9hDSW6tQ8&opensocial_app_id=2018662519&opensocial_app_url=http://wgw.collisioncourse.cc/opensocial.xml&oauth_version=1.0&oauth_timestamp=1231330978&oauth_consumer_key=ahWDDmGMkB4amIlKywHGfA&oauth_signature_method=HMAC-SHA1&oauth_nonce=1231330978966564000

    Lett ez:

    GET&http%3A%2F%2Fwgw.collisioncourse.cc%2Fopensocial%2Fping%3Fopensocial_owner_id%3Dsandbox.iwiw.hu%3AB9hDSW6tQ8%26opensocial_viewer_id%3Dsandbox.iwiw.hu%3AB9hDSW6tQ8%26opensocial_app_id%3D2018662519%26opensocial_app_url%3Dhttp%3A%2F%2Fwgw.collisioncourse.cc%2Fopensocial.xml%26oauth_version%3D1.0%26oauth_timestamp%3D1231330978%26oauth_consumer_key%3DahWDDmGMkB4amIlKywHGfA%26oauth_signature_method%3DHMAC-SHA1%26oauth_nonce%3D1231330978966564000

    oauth_signature ( ezt kene kapnom ) :

    zAbMelOluxldSp5e2o0oDqTxt1c=

    egy nagyon rovid ruby scripttel probalok letrehozni ezekbol signature-t a kovetkezokepp

    require ‘cgi’
    require ‘rubygems’
    require ‘hmac-sha1′
    require ‘base64′

    def escape(value)
    CGI.escape(value.to_s).gsub(”%7E”, ‘~’).gsub(”+”, “%20″)
    end

    def signature(base_string, consumer_secret,token_secret=”)
    secret=”#{escape(consumer_secret)}&#{escape(token_secret)}”
    Base64.encode64(HMAC::SHA1.digest(secret,base_string)).chomp.gsub(/\n/,”)
    end

    ezt a ket metodust a googlebol halasztam ki, de nagyon egyszeru az egesz: fogom a consumer secretet (azaz a megosztott titkos kulcsot) es a base stringet (plusz a token_secretet ami most ugye ures mert nem tortenik teljes oauth ciklus, csak az alairast ellenorzom), es a ketto osszegevel (siman concat es kell egy “&” kozejuk) kapom meg az alairast.

    Lefuttatva viszont..

    irb(main):014:0> base_string = ‘GET&http%3A%2F%2Fwgw.collisioncourse.cc%2Fopensocial%2Fping%3Fopensocial_owner_id%3Dsandbox.iwiw.hu%3AB9hDSW6tQ8%26opensocial_viewer_id%3Dsandbox.iwiw.hu%3AB9hDSW6tQ8%26opensocial_app_id%3D2018662519%26opensocial_app_url%3Dhttp%3A%2F%2Fwgw.collisioncourse.cc%2Fopensocial.xml%26oauth_version%3D1.0%26oauth_timestamp%3D1231330978%26oauth_consumer_key%3DahWDDmGMkB4amIlKywHGfA%26oauth_signature_method%3DHMAC-SHA1%26oauth_nonce%3D1231330978966564000′

    => “GET&http%3A%2F%2Fwgw.collisioncourse.cc%2Fopensocial%2Fping%3Fopensocial_owner_id%3Dsandbox.iwiw.hu%3AB9hDSW6tQ8%26opensocial_viewer_id%3Dsandbox.iwiw.hu%3AB9hDSW6tQ8%26opensocial_app_id%3D2018662519%26opensocial_app_url%3Dhttp%3A%2F%2Fwgw.collisioncourse.cc%2Fopensocial.xml%26oauth_version%3D1.0%26oauth_timestamp%3D1231330978%26oauth_consumer_key%3DahWDDmGMkB4amIlKywHGfA%26oauth_signature_method%3DHMAC-SHA1%26oauth_nonce%3D1231330978966564000″

    irb(main):015:0> consumer_secret = ‘0xeiHcIib7Uk0Kp1CBLDDNmzzqT8k3hRGrQFpCA1TqA’

    => “0xeiHcIib7Uk0Kp1CBLDDNmzzqT8k3hRGrQFpCA1TqA”

    irb(main):016:0> signature(base_string,consumer_secret)

    => “zRKR2mdMR0TBSWpOwiJr2cHlFUc=”

    ez nem egyezik a beerkezo alairassal (zAbMelOluxldSp5e2o0oDqTxt1c=)

    Mit csinalok rosszul? Rossz a base_string? A containerben nem igy all ossze?
    Bocs a hosszu kommentert, abban se vagyok biztos hogy fog ez megjelenni :-/

  2. 2 devblogger

    andras: a válasz is hosszú lesz. :)
    1) “en nem csak abban szeretnek biztos lenni hogy a keres az iwiw containerbol jott hanem hogy az en alkalmazasom kuldte”

    Akár nem töltesz fel kulcsot, és az iWiW publikus kulcsát használod az aláírás ellenőrzésére, akár pedig saját megosztott rejtett kulcsod feltöltése, és az azzal történő ellenőrzés lehetőséget kínál erre. Az iWiW titkos kulcsát nem tudhatja senki sem, így nem tud kérést hamisítani, és ugyanez igaz az általad az iWiW Fejlesztői Portálra feltöltött rejtett kulccsal is. Mind a két felállásban a kéréssel párhuzamosan érkezik az alkalmazás azonosítója is, így abban is biztos lehetsz, hogy az alkalmazásodtól jön a kérés.

    2) Miért nem egy biztosan működő Ruby library-val próbálkozol? :) Végigmegyek a lépéseken, és megnézem mi lehet a hiba, de biztosan jobban járnál egy kész könyvtárral.

  3. 3 Andras

    Koszonom a valaszt. Azzal probalkozom. Oauth gem + ruby oauth plugin. Invalid oauth request, 401 :-( odaig latom a logban hogy megtalalja a kulcsot a consumer_key -el, tehat tuti a megfelelo secret-el allitja elo a digest-et a base_url -re.. Csak azt szeretnem tudni a contenerben hogy all ossze a base_url

  4. 4 devblogger

    Andras: Meg kéne nézned a PHP kódot, szerintem elég átlátható. :)

  5. 5 andras

    Megneztem es en nem latom benne
    Lehet hogy felreertettel. Az oAuth specifikacio szerint az alairt request azt jelenti hogy a base_url -t, azaz a http metodus + hivott url + parameterek osszeget “irja ala” (HMAC-DSA1-el, a fenti ruby kodban ez HMAC::SHA1.digest(secret,base_string) ) a consumer_secret -el. Ebbol lesz a digest amit hozzaad a requesthez.

    Ennek a specifikacio szerint kell igy tortennie. Eleg jol leirjak ezen a cimen: http://oauth.net/core/1.0/#anchor14

    Atfogalmazva akkor: a consumer (iwiw container) hogy allitja ossze a 9.1. Signature Base String -et.

    koszi
    A

  6. 6 andras

    Amugy most vettem eszre hogy a base_string -bol ki kell hagyni a oauth_signature parametert. Megnezem igy is mas signature-t kapok-e :-)

  7. 7 devblogger

    Nem tartom jó ötletnek, hogy te próbálod meg összerakni a base_stringet. Nem csak az oauth_signature-t kell kihagyni, hanem a kulcsok szerint rendezni is kell a GET/POST paramétereket, és ha egy értékhez több paraméter is van, akkor azoknál érték szerint is kell csinálni egy rendezést - az OAuth szabvány szerint. Az egészet nem az iWiW találta ki, hanem egy teljesen szabványos dolog lett megvalósítva. A szabvány leprogramozása viszont nem könnyű és triviális… :)

  8. 8 Hangya

    Nagyon alapos, jó leírás, ez alapján sikerült működésre bírnom Python (Google App Engine) környezetben. A szerveroldali kód itt (webapp használata esetén):

    import ouath # Másoljuk a http://oauth.googlecode.com/svn/code/python/oauth/oauth.py fájlt a könyvtárba

    #…

    def get(self):
    self.response.headers['Content-Type'] = ‘text/html’

    request = oauth.OAuthRequest.from_request(’GET’, self.request.url, headers=self.request.headers, query_string=self.request.query_string)
    consumer = oauth.OAuthConsumer(self.request.get(’oauth_consumer_key’), ‘teszt kulacs’)
    signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
    signature_valid = signature_method.check_signature(request, consumer, None, self.request.get(’oauth_signature’))
    if signature_valid == True:
    self.response.out.write(’Hello ‘+self.request.get(’param’)+’!')
    else:
    self.response.out.write(’Sikertelen hitelesites’)
    #…

  9. 9 devblogger

    Köszönjük a többi Pythonos nevében is.

  10. 10 LacKac

    andras, devblogger: a rubys probléma megoldását, és további tippeket lásd a Virgo Undercode blogon: http://www.virgo.hu/undercode/2009/01/gadgetfejlesztes-rubyval-gadgeteer/

  1. 1 Undercode

Hagyj egy megjegyzést