Windows Live ID Web Authentication sample porten naar PHP

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Alex)
  • Registratie: Juni 2003
  • Laatst online: 21-08 11:20
Een aantal dagen terug heeft Microsoft een sample gepost van een manier om met Windows Live ID te authenticeren op je site, zie Microsoft Connect. Hierbij leveren ze twee stukken code mee, een deel C# en een code in Ruby.

Allemaal heel leuk en aardig, maar ik heb niets aan Ruby, dat ondersteunt mijn server niet. Ik ben daarom bezig geweest om de boel te porten naar PHP, dit is vrij aardig gelukt, alleen is er één deel nog niet helemaal gelukt: het gedeelte waar encryptie bij komt kijken.

Wanneer je bent ingelogd (vanaf de Live ID-site) wordt er een formulier teruggepost naar je website (<form action=returnurl method=POST>), hierin is onder andere een securitytoken vervat. Dit token is gecodeerd met AES128-encryptie en een geheime key, ook bevat het token een signature.

Ik heb geprobeerd dit om te zetten naar PHP (ik gebruik o.a. mcrypt) maar hij geeft daarna een fout dat de signature niet overeenkomt. Ik vermoed dus dat mijn code niet helemaal goed is omgezet, echter weet ik niet waar de fout zit.

Op http://admin.ownedia.com/liveid staat een voorbeeld van de code, en op http://admin.ownedia.com/liveid/source staat mijn source. Je hebt - wil je het testen - een apart account nodig. Je kunt niet inloggen met je normale Windows Live-account, je hebt een apart Live-Int account nodig.

In de Ruby-code van Microsoft staan 3 functies die met encryptie te maken hebben:
Ruby:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  def decodeToken(token)
    if (@cryptkey.nil? or @cryptkey.empty?)
      fatal("Error: decodeToken: Application secret was not set. Aborting.")
    end
    token =  u64(token)
    if (token.nil? or (token.size <= 16) or !(token.size % 16).zero?)
      debug("Error: decodeToken: Attempted to decode invalid token.")
      return
    end
    iv = token[0..15]
    crypted = token[16..-1]

    begin
      aes128cbc = OpenSSL::Cipher::AES128.new("CBC")
      aes128cbc.decrypt
      aes128cbc.iv = iv
      aes128cbc.key = @cryptkey
      decrypted = aes128cbc.update(crypted) + aes128cbc.final
    rescue Exception => e
      debug("Error: decodeToken: Decryption failed: #{token}, #{e}")
      return
    end
    decrypted
  end

  def signToken(token)
    if (@signkey.nil? or @signkey.empty?)
      fatal("Error: signToken: Application secret was not set. Aborting.")
    end
    begin
      digest = OpenSSL::Digest::SHA256.new
      return OpenSSL::HMAC.digest(digest, @signkey, token)
    rescue Exception => e
      debug("Error: signToken: Signing failed: #{token}, #{e}")
      return
    end
  end

  def derive(secret, nonce)
    begin
      fatal("Nil/empty secret.") if (secret.nil? or secret.empty?)
      key = nonce + secret
      key = OpenSSL::Digest::SHA256.digest(key)
      return key[0..15]
    rescue Exception => e
      debug("Error: derive: #{e}")
      return
    end
  end


Die heb ik omgezet naar deze code:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    function decodeToken($token)
    {
        if(!isset($this->cryptKey) || $this->cryptKey == "")
        {
            die("Error: decodeToken: Application secret was not set. Aborting.");
        }
        $token = base64_decode(urldecode($token));

        if(!isset($token) || strlen($token) < 16 || (strlen($token) % 16 != 0))
        {
            die("Error: decodeToken: Attempted to decode invalid token.");
        }

        $iv = substr($token, 0, 16);
        $crypted = substr($token, 16, -1);
    
        $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->cryptKey, $crypted, "cbc", $iv);
        
        return $decrypted;
    }
    function signToken($token)
    {
        if(!isset($this->signKey) || $this->signKey == "")
        {
            die("Error: signToken: Application secret was not set. Aborting.");
        }

        $uitvoer = hash_hmac('sha256', $token, $this->signKey);
        echo $uitvoer;
        
        return $uitvoer;
    }
    function derive($secret, $nonce)
    {
        if(!isset($secret) || $secret == "")
        {
            die("Error: derive: Null/empty secret");
        }

        try
        {
            $key = $nonce . $secret;
            $key = hash('sha256', $key);
            return substr($key, 0, 16);
        }
        catch (Exception $e)
        {
            die("Error: derive: ".$e);
        }
    }


Ook is er een functie 'validateToken', die controleert of alles correct is:
Ruby:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  def validateToken(token)
    if (token.nil? or token.empty?)
      debug("Error: validateToken: nil/empty token.")
      return
    end
    body, sig = token.split("&sig=")
    if (body.nil? or sig.nil?)
      debug("Error: validateToken: Invalid token: #{token}")
      return
    end
    sig = u64(sig)
    return token if (sig == signToken(body))
    debug("Error: validateToken: Signature did not match.")
    return
  end


(u64 is een procedure van MS die de ontvangen gegevens base64-decodeert)

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    function validateToken($token)
    {
        if (!isset($token) || $token == "")
        {
            die("Error: validateToken: nil/empty token.");
        }
        $tokenArr = split("&sig=", $token);
        $body = $tokenArr[0];
        $sig = $tokenArr[1];

        if($body == "")
        {
            die("Error: validateToken: Invalid token: $token");
        }
        $sig = base64_decode(urldecode($sig));

        if($sig == $this->signToken($body))
        {
            return $token;
        }
        else
        {
            die("Error: validateToken: Signature did not match.");
        }
    }



Ergens hier zit dus een fout, maar ik weet niet waar. Heeft iemand van jullie toevallig een idee waar ik moet zoeken? Alvast vriendelijk bedankt :)

We are shaping the future


Acties:
  • 0 Henk 'm!

  • Reptile209
  • Registratie: Juni 2001
  • Laatst online: 20:40

Reptile209

- gers -

Algemeen: ik denk dat je mazzel moet hebben als iemand de lappen code in beide talen naast elkaar gaat leggen om "de fout" te vinden. Zit er geen beschrijving bij die stap-voor-stap dumpt wat de output zou moeten zijn? En heb je zelf al eens in iedere stap gekeken of alle variabelen "logische" waarden hebben?
Het enige dat mij opviel, is dat je PHP-functies gebruik maken van URLDecode() en dat de Ruby versie dat niet doet. Klopt dat wel? Zo niet, zie hierboven :)

Zo scherp als een voetbal!


Acties:
  • 0 Henk 'm!

  • Alex)
  • Registratie: Juni 2003
  • Laatst online: 21-08 11:20
Ik heb geen beschikking over een Ruby-omgeving dus ik kan niet kijken wat de Ruby-output moet zijn, in de PHP-versie krijg ik op verschillende stappen gewoon totale nonsens als output (vreemde tekens), dit is nog gecodeerde code.

In de Ruby-code wordt ook een url-decode-functie gebruikt:
Ruby:
1
2
3
4
def u64(s)
    return unless s
    Base64.decode64 CGI.unescape(s)
  end

Ik vond deze functie alleen niet nodig om te vermelden. De token wordt aangeleverd als urlencode(base64_encode($token));, dat draai ik dus weer om :)

In de meegeleverde whitepaper staat alleen hoe het moet worden geïmplementeerd, de interne werking (+returnwaarden) staat niet vermeld.

[ Voor 11% gewijzigd door Alex) op 27-05-2007 22:37 ]

We are shaping the future


Acties:
  • 0 Henk 'm!

  • Sjaaky
  • Registratie: Oktober 2000
  • Laatst online: 20-09 23:02
Ik zou lokaal ruby installeren en de voorbeeldcode werkend krijgen. Vervolgens logging toevoegen de tussenresultaten proberen te reproduceren met php. Anders is het zoeken naar een naald in een hooiberg.

[ Voor 10% gewijzigd door Sjaaky op 28-05-2007 10:54 ]


Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
Alex) schreef op zondag 27 mei 2007 @ 22:36:
Ik heb geen beschikking over een Ruby-omgeving dus ik kan niet kijken wat de Ruby-output moet zijn,
http://www.ruby-lang.org/en/downloads/

|:(

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.