You are here

abstract class GoogleAuthenticator in Google Authenticator login 7

Same name and namespace in other branches
  1. 6 ga4php.php \GoogleAuthenticator

@file Abstract GoogleAuthenticator class.

Hierarchy

Expanded class hierarchy of GoogleAuthenticator

File

./ga4php.php, line 8
Abstract GoogleAuthenticator class.

View source
abstract class GoogleAuthenticator {

  /**
   * Constructor.
   */
  public function __construct($totpskew = 1, $hotpskew = 10, $hotphuntvalue = 200000) {

    // The hotpskew is how many tokens forward we look to find the input
    // code the user used.
    $this->hotpSkew = $hotpskew;

    // The totpskew value is how many tokens either side of the current
    // token we should check, based on a time skew.
    $this->totpSkew = $totpskew;

    // The hotphuntvalue is what we use to resync tokens.
    // when a user resyncs, we search from 0 to $hutphutvalue to find
    // the two token codes the user has entered - 200000 seems like overkill
    // really as i cant imagine any token out there would ever make it
    // past 200000 token code requests.
    $this->hotpHuntValue = $hotphuntvalue;
  }

  // Pure abstract functions that need to be overloaded when
  // creating a sub class.
  public abstract function getData($username);
  public abstract function putData($username, $data);
  public abstract function getUsers();

  /**
   * Create an empty data structure, filled with some defaults.
   */
  public function createEmptyData() {
    $data["tokenkey"] = "";
    $data["tokentype"] = "HOTP";
    $data["tokentimer"] = 30;
    $data["tokencounter"] = 1;
    $data["tokenalgorithm"] = "SHA1";
    $data["user"] = "";
    return $data;
  }

  /**
   * Set custom data.
   */
  public function setCustomData($username, $data) {
    $data = $this
      ->internalGetData($username);
    $data["user"] = $key;
    $this
      ->internalPutData($username, $data);
  }

  /**
   * Load custom data.
   */
  public function getCustomData($username) {
    $data = $this
      ->internalGetData($username);
    $custom = $data["user"];
    return $custom;
  }

  /**
   * Load internal data.
   */
  public function internalGetData($username) {
    $data = $this
      ->getData($username);
    $deco = unserialize(base64_decode($data));
    if (!$deco) {
      $deco = $this
        ->createEmptyData();
    }
    return $deco;
  }

  /**
   * Store data.
   */
  public function internalPutData($username, $data) {
    if ($data == "") {
      $enco = "";
    }
    else {

      // $data['user'] = $username;
      $enco = base64_encode(serialize($data));
    }
    return $this
      ->putData($username, $enco);
  }

  /**
   * Sets the token type the user it going to use.
   */
  public function setTokenType($username, $tokentype) {
    $tokentype = strtoupper($tokentype);
    if ($tokentype != "HOTP" && $tokentype != "TOTP") {
      $this->errorText = "Invalid Token Type";
      return FALSE;
    }
    $data = $this
      ->internalGetData($username);
    $data["tokentype"] = $tokentype;
    return $this
      ->internalPutData($username, $data);
  }

  /**
   * Create a user.
   */
  public function setUser($username, $ttype = "HOTP", $key = "", $hexkey = "") {
    $ttype = strtoupper($ttype);
    if ($ttype != "HOTP" && $ttype != "TOTP") {
      return FALSE;
    }
    if ($key == "") {
      $key = $this
        ->createBase32Key();
    }
    $hkey = $this
      ->helperb322hex($key);
    if ($hexkey != "") {
      $hkey = $hexkey;
    }
    $token = $this
      ->internalGetData($username);
    $token["tokenkey"] = $hkey;
    $token["tokentype"] = $ttype;
    if (!$this
      ->internalPutData($username, $token)) {
      return FALSE;
    }
    return $key;
  }

  /**
   * Determine if the user has an actual token.
   */
  public function hasToken($username) {
    $token = $this
      ->internalGetData($username);

    // @todo change this to a pattern match for an actual key.
    if (!isset($token["tokenkey"])) {
      return FALSE;
    }
    if ($token["tokenkey"] == "") {
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Sets a users key.
   */
  public function setUserKey($username, $key) {
    $token = $this
      ->internalGetData($username);
    $token["tokenkey"] = $key;
    $this
      ->internalPutData($username, $token);

    // @todo error checking.
    return TRUE;
  }

  /**
   * Delete a user.
   */
  public function deleteUser($username) {
    $this
      ->internalPutData($username, "");
  }

  /**
   * Authenticate a user using a code.
   */
  public function authenticateUser($username, $code) {
    if (preg_match("/[0-9][0-9][0-9][0-9][0-9][0-9]/", $code) < 1) {
      $this->errorText = "6 digits please";
      return FALSE;
    }
    $tokendata = $this
      ->internalGetData($username);
    if ($tokendata["tokenkey"] == "") {
      $this->errorText = "No Assigned Token";
      return FALSE;
    }

    // @todo check return value.
    $ttype = $tokendata["tokentype"];
    $tlid = $tokendata["tokencounter"];
    $tkey = $tokendata["tokenkey"];
    switch ($ttype) {
      case "HOTP":
        $st = $tlid + 1;
        $en = $tlid + $this->hotpSkew;
        for ($i = $st; $i < $en; $i++) {
          $stest = $this
            ->oathHotp($tkey, $i);
          if ($code == $stest) {
            $tokendata["tokencounter"] = $i;
            $this
              ->internalPutData($username, $tokendata);
            return TRUE;
          }
        }
        return FALSE;
      case "TOTP":
        $t_now = time();
        $t_ear = $t_now - $this->totpSkew * $tokendata["tokentimer"];
        $t_lat = $t_now + $this->totpSkew * $tokendata["tokentimer"];
        $t_st = (int) ($t_ear / $tokendata["tokentimer"]);
        $t_en = (int) ($t_lat / $tokendata["tokentimer"]);
        for ($i = $t_st; $i <= $t_en; $i++) {
          $stest = $this
            ->oathHotp($tkey, $i);
          error_log("testing code: {$code}, {$stest}, {$tkey}\n");
          if ($code == $stest) {
            return TRUE;
          }
        }
        break;
      default:
        return FALSE;
    }
    return FALSE;
  }

  /**
   * Resync codes.
   */
  public function resyncCode($username, $code1, $code2) {
    $tokendata = $this
      ->internalGetData($username);

    // @todo check return value.
    $ttype = $tokendata["tokentype"];
    $tlid = $tokendata["tokencounter"];
    $tkey = $tokendata["tokenkey"];
    if ($tkey == "") {
      $this->errorText = "No Assigned Token";
      return FALSE;
    }
    switch ($ttype) {
      case "HOTP":
        $st = 0;
        $en = $this->hotpHuntValue;
        for ($i = $st; $i < $en; $i++) {
          $stest = $this
            ->oathHotp($tkey, $i);
          if ($code1 == $stest) {
            $stest2 = $this
              ->oathHotp($tkey, $i + 1);
            if ($code2 == $stest2) {
              $tokendata["tokencounter"] = $i + 1;
              $this
                ->internalPutData($username, $tokendata);
              return TRUE;
            }
          }
        }
        return FALSE;
      case "TOTP":
        break;
      default:
        echo "how the frig did i end up here?";
    }
    return FALSE;
  }

  /**
   * Gets the error text associated with the last error.
   */
  public function getErrorText() {
    return $this->errorText;
  }

  /**
   * Create a url compatibile with google authenticator.
   */
  public function createURL($user) {

    // HOTP needs to be lowercase.
    $data = $this
      ->internalGetData($user);
    $toktype = $data["tokentype"];
    $key = $this
      ->helperhex2b32($data["tokenkey"]);

    // Token counter should be one more then current token value.
    $counter = $data["tokencounter"] + 1;
    $toktype = strtolower($toktype);
    if ($toktype == "hotp") {
      $url = "otpauth://{$toktype}/{$user}?secret={$key}&counter={$counter}";
    }
    else {
      $url = "otpauth://{$toktype}/{$user}?secret={$key}";
    }
    return $url;
  }

  /**
   * Creates a base 32 key (random).
   */
  public function createBase32Key() {
    $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    $key = "";
    for ($i = 0; $i < 16; $i++) {
      $offset = rand(0, strlen($alphabet) - 1);
      $key .= $alphabet[$offset];
    }
    return $key;
  }

  /**
   * Get hex key.
   */
  public function getKey($username) {
    $data = $this
      ->internalGetData($username);
    $key = $data["tokenkey"];
    return $key;
  }

  /**
   * Get token type.
   */
  public function getTokenType($username) {
    $data = $this
      ->internalGetData($username);
    $toktype = $data["tokentype"];
    return $toktype;
  }

  /**
   * Convert b32 to hex.
   */
  public function helperb322hex($b32) {
    $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    $out = "";
    $dous = "";
    for ($i = 0; $i < strlen($b32); $i++) {
      $in = strrpos($alphabet, $b32[$i]);
      $b = str_pad(base_convert($in, 10, 2), 5, "0", STR_PAD_LEFT);
      $out .= $b;
      $dous .= $b . ".";
    }
    $ar = str_split($out, 20);
    $out2 = "";
    foreach ($ar as $val) {
      $rv = str_pad(base_convert($val, 2, 16), 5, "0", STR_PAD_LEFT);
      $out2 .= $rv;
    }
    return $out2;
  }

  /**
   * Convert hax to b32.
   */
  public function helperhex2b32($hex) {
    $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    $ar = str_split($hex, 5);
    $out = "";
    foreach ($ar as $var) {
      $bc = base_convert($var, 16, 2);
      $bin = str_pad($bc, 20, "0", STR_PAD_LEFT);
      $out .= $bin;
    }
    $out2 = "";
    $ar2 = str_split($out, 5);
    foreach ($ar2 as $var2) {
      $bc = base_convert($var2, 2, 10);
      $out2 .= $alphabet[$bc];
    }
    return $out2;
  }

  /**
   * Create HOTP.
   */
  public function oathHotp($key, $counter) {
    $key = pack("H*", $key);
    $cur_counter = array(
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
    );
    for ($i = 7; $i >= 0; $i--) {
      $cur_counter[$i] = pack('C*', $counter);
      $counter = $counter >> 8;
    }
    $bin_counter = implode($cur_counter);

    // Pad to 8 chars.
    if (strlen($bin_counter) < 8) {
      $bin_counter = str_repeat(chr(0), 8 - strlen($bin_counter)) . $bin_counter;
    }

    // HMAC.
    $hash = hash_hmac('sha1', $bin_counter, $key);
    return str_pad($this
      ->oathTruncate($hash), 6, "0", STR_PAD_LEFT);
  }

  /**
   * Truncate.
   */
  public function oathTruncate($hash, $length = 6) {

    // Convert to dec,
    foreach (str_split($hash, 2) as $hex) {
      $hmac_result[] = hexdec($hex);
    }

    // Find offset.
    $offset = $hmac_result[19] & 0xf;

    // Algorithm from RFC.
    return (($hmac_result[$offset + 0] & 0x7f) << 24 | ($hmac_result[$offset + 1] & 0xff) << 16 | ($hmac_result[$offset + 2] & 0xff) << 8 | $hmac_result[$offset + 3] & 0xff) % pow(10, $length);
  }

  // Some private data bits.
  private $getDatafunction;
  private $putDatafunction;
  private $errorText;
  private $errorCode;
  protected $hotpSkew;
  protected $totpSkew;
  protected $hotpHuntValue;

}

Members

Namesort descending Modifiers Type Description Overrides
GoogleAuthenticator::$errorCode private property
GoogleAuthenticator::$errorText private property
GoogleAuthenticator::$getDatafunction private property
GoogleAuthenticator::$hotpHuntValue protected property
GoogleAuthenticator::$hotpSkew protected property
GoogleAuthenticator::$putDatafunction private property
GoogleAuthenticator::$totpSkew protected property
GoogleAuthenticator::authenticateUser public function Authenticate a user using a code. 1
GoogleAuthenticator::createBase32Key public function Creates a base 32 key (random).
GoogleAuthenticator::createEmptyData public function Create an empty data structure, filled with some defaults. 1
GoogleAuthenticator::createURL public function Create a url compatibile with google authenticator. 1
GoogleAuthenticator::deleteUser public function Delete a user.
GoogleAuthenticator::getCustomData public function Load custom data.
GoogleAuthenticator::getData abstract public function 1
GoogleAuthenticator::getErrorText public function Gets the error text associated with the last error.
GoogleAuthenticator::getKey public function Get hex key.
GoogleAuthenticator::getTokenType public function Get token type.
GoogleAuthenticator::getUsers abstract public function 1
GoogleAuthenticator::hasToken public function Determine if the user has an actual token.
GoogleAuthenticator::helperb322hex public function Convert b32 to hex.
GoogleAuthenticator::helperhex2b32 public function Convert hax to b32.
GoogleAuthenticator::internalGetData public function Load internal data.
GoogleAuthenticator::internalPutData public function Store data.
GoogleAuthenticator::oathHotp public function Create HOTP.
GoogleAuthenticator::oathTruncate public function Truncate.
GoogleAuthenticator::putData abstract public function 1
GoogleAuthenticator::resyncCode public function Resync codes.
GoogleAuthenticator::setCustomData public function Set custom data.
GoogleAuthenticator::setTokenType public function Sets the token type the user it going to use.
GoogleAuthenticator::setUser public function Create a user.
GoogleAuthenticator::setUserKey public function Sets a users key.
GoogleAuthenticator::__construct public function Constructor.