Location: PHPKode > projects > SolarPHP > solar-system-1.1.1/solar/source/solar/Solar/Auth/Adapter/Typekey.php
<?php
/**
 * 
 * Authentication adapter for TypeKey.
 * 
 * Requires that PHP have been compiled using "--enable-bcmath" or
 * '--with-gmp'.
 * 
 * Based largely on PEAR Auth_Typekey proposal by Daiji Hirata, in
 * particular the DSA signature verification methods.  See the original
 * code at <http://www.uva.ne.jp/Auth_TypeKey/Auth_TypeKey.phps>.
 * 
 * Developed for, and then donated by, Mashery.com <http://mashery.com>.
 * 
 * For more info on TypeKey, see ...
 * 
 * * <http://www.sixapart.com/typekey/api>
 * 
 * * <http://www.sixapart.com/movabletype/docs/tk-apps>
 * 
 * @category Solar
 * 
 * @package Solar_Auth
 * 
 * @author Daiji Hirata <hide@address.com>
 * 
 * @author Paul M. Jones <hide@address.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id: Typekey.php 4405 2010-02-18 04:27:25Z pmjones $
 * 
 */
class Solar_Auth_Adapter_Typekey extends Solar_Auth_Adapter
{
    /**
     * 
     * Default configuration values.
     * 
     * @config string token The TypeKey "site token" id against which
     * authentication requests will be made.
     * 
     * @config int window The signature should have been generated
     * within this many seconds of "now". Default is 10 seconds, to
     * allow for long network latency periods.
     * 
     * @config dependency cache A Solar_Cache dependency for storing 
     * the TypeKey public key data.
     * 
     * @config string cache_key When using a cache, the entry key for
     * the TypeKey public key data.  Default 'typekey_pubkey'.
     * 
     * @var array
     * 
     */
    protected $_Solar_Auth_Adapter_Typekey = array(
        'token'     => null,
        'window'    => 10,
        'cache'     => null,
        'cache_key' => 'typekey_pubkey',
    );
    
    /**
     * 
     * A reconstructed "message" to be verified for validity.
     * 
     * @var string
     * 
     * @see Solar_Auth_Adapter_Typekey::isLoginValid()
     * 
     * @see Solar_Auth_Adapter_Typekey::_processLogin()
     * 
     */
    protected $_msg;
    
    /**
     * 
     * Public key as fetched from TypeKey server.
     * 
     * @var string
     * 
     * @see Solar_Auth_Adapter_Typekey::_fetchKeyData()
     * 
     */
    protected $_key;
    
    /**
     * 
     * DSA signature extracted from login attempt GET request vars.
     * 
     * @var string
     * 
     * @see Solar_Auth_Adapter_Typekey::isLoginValid()
     * 
     * @see Solar_Auth_Adapter_Typekey::_processLogin()
     * 
     */
    protected $_sig;
    
    /**
     * 
     * Use bcmath or gmp extension to verify signatures?
     * 
     * @var string
     * 
     */
    protected $_ext;
    
    /**
     * 
     * Cache for the TypeKey public key data.
     * 
     * @var Solar_Cache
     * 
     */
    protected $_cache;
    
    /**
     * 
     * Checks to make sure a GMP or BCMath extension is available.
     * 
     * @return void
     * 
     */
    protected function _preConfig()
    {
        parent::_preConfig();
        if (extension_loaded('gmp')) {
            $this->_ext = 'gmp';
        } elseif (extension_loaded('bcmath')) {
            $this->_ext = 'bc';
        } else {
            throw $this->_exception('ERR_EXTENSION_NOT_LOADED', array(
                'extension' => '(bcmath || gmp)'
            ));
        }
    }
    
    /**
     * 
     * Post-construction tasks to complete object construction.
     * 
     * @return void
     * 
     */
    protected function _postConstruct()
    {
        parent::_postConstruct();
        if ($this->_config['cache']) {
            $this->_cache = Solar::dependency(
                'Solar_Cache',
                $this->_config['cache']
            );
        }
    }
    
    /**
     * 
     * Is the current page-load a login request?
     * 
     * We can tell because there will be certain GET params in place ...
     * 
     *     &ts=1149633028
     *     &email=user%40example.com
     *     &name=handle
     *     &nick=Moni%20Kerr
     *     &sig=PBG7mN48V9f83hOX5Ao+X9GbmUU=:maoKWgIZpcF1qVFUHf8GbFooAFc=
     * 
     * @return bool
     * 
     */
    public function isLoginRequest()
    {
        return ! empty($this->_request->get['email']) &&
               ! empty($this->_request->get['name']) &&
               ! empty($this->_request->get['nick']) &&
               ! empty($this->_request->get['ts']) &&
               ! empty($this->_request->get['sig']);
    }
    
    /**
     * 
     * Fetches the public key data from TypeKey.
     * 
     * If a cache object was injected at construction time, uses
     * a cached version of the public key instead of hitting the
     * TypeKey server.
     * 
     * The URI used is "http://www.typekey.com/extras/regkeys.txt".
     * 
     * @return array An array with keys 'p', 'q', 'g', and 'pub_key'
     * as extracted from the fetched key string.
     * 
     */
    protected function _fetchKeyData()
    {
        $cache_key = $this->_config['cache_key'];
        if ($this->_cache) {
            $info = $this->_cache->fetch($cache_key);
            if ($info) {
                // cache hit
                return $info;
            }
        }
        
        // cache miss, or no cache.  get from typekey.
        $src = file_get_contents('http://www.typekey.com/extras/regkeys.txt');
        $lines = explode(' ', trim($src));
        foreach ($lines as $line) {
            $val = explode('=', $line);
            $info[$val[0]] = $val[1];
        }
        
        // save in the cache?
        if ($this->_cache) {
            $this->_cache->save($cache_key, $info);
        }
        
        // done
        return $info;
    }
    
    /**
     * 
     * Processes login credentials using either GMP or bcmath functions.
     * 
     * @return mixed An array of verified user information, or boolean false
     * if verification failed.
     * 
     */
    protected function _processLogin()
    {
        // get data from the request.
        $email = $this->_request->get('email');
        $name  = $this->_request->get('name');
        $nick  = $this->_request->get('nick');
        $ts    = $this->_request->get('ts');
        
        // are we in the allowed time window?
        if (time() - $ts > $this->_config['window']) {
            // possible replay attack.
            return 'REPLAY';
        }
        
        // get the signature values from the login. note that the sig
        // values need to have pluses converted to spaces because
        // urldecode() doesn't do that for us. thus, we have to re-
        // encode, the raw-decode it.
        $this->_sig = rawurldecode(
            urlencode($this->_request->get('sig'))
        );
        
        // re-create the message for signature comparison.
        // <email>::<name>::<nick>::<ts>
        $this->_msg = "$email::$name::$nick::$ts";
        
        // get the TypeKey public key data
        $this->_key = $this->_fetchKeyData();
        
        // what method for verification?
        $method = '_verify_' . $this->_ext;
        
        // verification routine
        if ($this->$method()) {
            return array(
                'handle'  => $name,  // username
                'email'   => $email, // email
                'moniker' => $nick,  // display name
            );
        } else {
            return false;
        }
    }
    
    /**
     * 
     * DSA verification using GMP.
     * 
     * Uses $this->_msg, $this->_key, and $this->_sig as the data
     * sources.
     * 
     * @return bool True if the message signature is verified using the
     * DSA public key.
     * 
     */
    protected function _verify_gmp()
    {
        $msg = $this->_msg;
        $key = $this->_key;
        $sig = $this->_sig;
        
        list($r_sig, $s_sig) = explode(":", $sig);
        $r_sig = base64_decode($r_sig);
        $s_sig = base64_decode($s_sig);
        
        foreach ($key as $i => $v) {
            $key[$i] = gmp_init($v);
        }
        
        $s1 = gmp_init($this->_gmp_bindec($r_sig));
        $s2 = gmp_init($this->_gmp_bindec($s_sig));
        
        $w = gmp_invert($s2, $key['q']);
        
        $hash_m = gmp_init('0x' . sha1($msg));
        
        $u1 = gmp_mod(gmp_mul($hash_m, $w), $key['q']);
        $u2 = gmp_mod(gmp_mul($s1, $w), $key['q']);
        
        $v = gmp_mod( 
                gmp_mod( 
                    gmp_mul(
                        gmp_powm($key['g'], $u1, $key['p']), 
                        gmp_powm($key['pub_key'], $u2, $key['p'])
                    ), 
                    $key['p']
                ), 
             $key['q']
        );
        
        if (gmp_cmp($v, $s1) == 0) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * 
     * Converts a binary to a decimal value using GMP functions.
     * 
     * @param string $bin The original binary value string.
     * 
     * @return string Decimal value string converted from $bin.
     * 
     */
    protected function _gmp_bindec($bin) 
    {
        $dec = gmp_init(0);
        while (strlen($bin)) {
            $i = ord(substr($bin, 0, 1));
            $dec = gmp_add(gmp_mul($dec, 256), $i);
            $bin = substr($bin, 1);
        }
        return gmp_strval($dec);
    }
    
    /**
     * 
     * DSA verification using bcmath.
     * 
     * Uses $this->_msg, $this->_key, and $this->_sig as the data
     * sources.
     * 
     * @return bool True if the message signature is verified using the
     * DSA public key.
     * 
     */
    protected function _verify_bc()
    {
        $msg = $this->_msg;
        $key = $this->_key;
        $sig = $this->_sig;
        
        list($r_sig, $s_sig) = explode(':', $sig);
        
        $r_sig = base64_decode($r_sig);
        $s_sig = base64_decode($s_sig);
        
        $s1 = $this->_bc_bindec($r_sig);
        $s2 = $this->_bc_bindec($s_sig);
        
        $w = $this->_bc_invert($s2, $key['q']);
        $hash_m = $this->_bc_hexdec(sha1($msg));
        
        $u1 = bcmod(bcmul($hash_m, $w), $key['q']);
        $u2 = bcmod(bcmul($s1, $w), $key['q']);
        
        $v = bcmod(
                bcmod(
                    bcmul(
                        bcmod(bcpowmod($key['g'], $u1, $key['p']), $key['p']),
                        bcmod(bcpowmod($key['pub_key'], $u2, $key['p']), $key['p'])
                    ),
                    $key['p']
                ),
             $key['q']
        );
        
        return (bool) bccomp($v, $s1) == 0;
    }
    
    /**
     * 
     * Converts a hex value string to a decimal value string using
     * bcmath functions.
     * 
     * @param string $hex The original hex value string.
     * 
     * @return string Decimal string converted from $hex.
     * 
     */
    protected function _bc_hexdec($hex)
    {
        $dec = '0';
        while (strlen($hex)) {
            $i = hexdec(substr($hex, 0, 4));
            $dec = bcadd(bcmul($dec, 65536), $i);
            $hex = substr($hex, 4);
        }
        return $dec;
    }
    
    /**
     * 
     * Converts a binary value string to a decimal value string using
     * bcmath functions.
     * 
     * @param string $bin The original binary value string.
     * 
     * @return string Decimal value string converted from $bin.
     * 
     */
    protected function _bc_bindec($bin)
    {
        $dec = '0';
        while (strlen($bin)) {
            $i = ord(substr($bin, 0, 1));
            $dec = bcadd(bcmul($dec, 256), $i);
            $bin = substr($bin, 1);
        }
        return $dec;
    }
    
    /**
     * 
     * Inverts two values using bcmath functions.
     * 
     * @param string $x First value.
     * 
     * @param string $y Second value.
     * 
     * @return string The inverse of $x and $y.
     * 
     */
    protected function _bc_invert($x, $y) 
    {
        while (bccomp($x, 0)<0) { 
            $x = bcadd($x, $y);
        }
        $r = $this->_bc_exgcd($x, $y);
        if ($r[2] == 1) {
            $a = $r[0];
            while (bccomp($a, 0) < 0) {
                $a = bcadd($a, $y);
            }
            return $a;
        } else {
            return false;
        }
    }
    
    /**
     * 
     * Finds the extended greatest-common-denominator of two values
     * using bcmath functions.
     * 
     * @param string $x First value.
     * 
     * @param string $y Second value.
     * 
     * @return array Extended GCD of $x and $y.
     * 
     */
    protected function _bc_exgcd($x, $y) 
    {
        $a0 = 1; $a1 = 0;
        
        $b0 = 0; $b1 = 1;
        
        $c = 0;
        
        while ($y > 0) {
            $q = bcdiv($x, $y, 0);
            $r = bcmod($x, $y);
            
            $x = $y; $y = $r;
            
            $a2 = bcsub($a0, bcmul($q, $a1));
            $b2 = bcsub($b0, bcmul($q, $b1));
            
            $a0 = $a1; $a1 = $a2;
            
            $b0 = $b1; $b1 = $b2;
        }
        
        return array($a0, $b0, $x);
    }
}
Return current item: SolarPHP