plugins/yubikey/corelib.php
author Dan
Thu, 26 Feb 2009 01:19:45 -0500
changeset 2 6edc6ebb3b39
parent 0 9d2c4f04a0d0
child 3 d0fe7acaf0e8
permissions -rw-r--r--
Minor: if input OTP is empty and password looks like OTP, now copies password to OTP in memory and treats password field as OTP. Will require patch in Enano trunk to work for html login.
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
0
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
     1
<?php
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
     2
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
     3
define('YK_SEC_NORMAL_USERNAME', 1);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
     4
define('YK_SEC_NORMAL_PASSWORD', 2);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
     5
define('YK_SEC_ELEV_USERNAME', 4);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
     6
define('YK_SEC_ELEV_PASSWORD', 8);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
     7
define('YK_SEC_ALLOW_NO_OTP', 16);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
     8
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
     9
define('YK_DEFAULT_VERIFY_URL', 'http://api.yubico.com/wsapi/verify');
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    10
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    11
function generate_yubikey_field($name = 'yubikey_otp', $value = false)
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    12
{
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    13
  global $lang;
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    14
  
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    15
  $fid = substr(sha1(microtime() . mt_rand()), 0, 12);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    16
  $class = $value ? 'wasfull' : 'wasempty';
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    17
  $html = '<input id="yubifield' . $fid . '" class="' . $class . '" type="hidden" name="' . $name . '" value="' . ( is_string($value) ? $value : '' ) . '" />';
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    18
  if ( $value )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    19
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    20
    $html .= '<span id="yubistat' . $fid . '" class="yubikey_status enrolled">' . $lang->get('yubiauth_ctl_status_enrolled') . '</span>';
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    21
    $atext = $lang->get('yubiauth_ctl_btn_change_key');
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    22
    $classadd = ' abutton_green';
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    23
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    24
  else
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    25
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    26
    $html .= '<span id="yubistat' . $fid . '" class="yubikey_status empty">' . $lang->get('yubiauth_ctl_status_empty') . '</span>';
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    27
    $atext = $lang->get('yubiauth_ctl_btn_enroll');
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    28
    $classadd = '';
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    29
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    30
  $html .= ' <a class="abutton' . $classadd . ' yubikey_enroll" onclick="yk_mb_init(\'yubifield' . $fid . '\', \'yubistat' . $fid . '\'); return false;" href="#enroll">' . $atext . '</a>';
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    31
  if ( $value )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    32
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    33
    $html .= ' <a class="abutton abutton_red yubikey_enroll" onclick="yk_clear(\'yubifield' . $fid . '\', \'yubistat' . $fid . '\'); return false;" href="#enroll">'
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    34
             . $lang->get('yubiauth_ctl_btn_clear') .
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    35
             '</a>';
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    36
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    37
  $html = '<noscript><input type="text" name="' . $name . '" class="yubikey_noscript" value="' . ( is_string($value) ? $value : '' ) . '" /> </noscript>'
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    38
          . $html; // '<script type="text/javascript">document.write(unescape("' . rawurlencode($html) . '"));</script>';
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    39
  return $html;
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    40
}
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    41
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    42
function yubikey_validate_otp($otp)
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    43
{
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    44
  $api_key = getConfig('yubikey_api_key');
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    45
  $api_id  = getConfig('yubikey_api_key_id');
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    46
  if ( !$api_key || !$api_id )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    47
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    48
    return array(
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    49
        'success' => false,
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    50
        'error' => 'missing_api_key'
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    51
      );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    52
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    53
  if ( !preg_match('/^[cbdefghijklnrtuv]{44}$/', $otp) )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    54
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    55
    return array(
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    56
        'success' => false,
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    57
        'error' => 'otp_invalid_chars'
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    58
      );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    59
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    60
  // make HTTP request
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    61
  require_once( ENANO_ROOT . '/includes/http.php' );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    62
  $auth_url = getConfig('yubikey_auth_server', YK_DEFAULT_VERIFY_URL);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    63
  $auth_url = preg_replace('#^https?://#i', '', $auth_url);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    64
  if ( !preg_match('#^(\[?[a-z0-9-:]+(?:\.[a-z0-9-:]+\]?)*)(/.*)$#', $auth_url, $match) )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    65
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    66
    return array(
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    67
        'success' => false,
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    68
        'error' => 'invalid_auth_url'
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    69
      );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    70
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    71
  $auth_server =& $match[1];
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    72
  $auth_uri =& $match[2];
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    73
  $req = new Request_HTTP($auth_server, $auth_uri);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    74
  $req->add_get('id', strval($api_id));
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    75
  $req->add_get('otp', $otp);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    76
  $req->add_get('h', yubikey_sign($req->parms_get));
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    77
  
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    78
  $response = $req->get_response_body();
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    79
  
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    80
  if ( $req->response_code != HTTP_OK )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    81
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    82
    return array(
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    83
        'success' => false,
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    84
        'error' => 'http_response_error'
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    85
      );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    86
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    87
  $response = trim($response);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    88
  $response_nosig = preg_replace('/^h=(.+?)$/m', '', $response);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    89
  if ( !preg_match_all('/^([a-z0-9_]+)=(.*?)$/m', $response, $matches) )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    90
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    91
    return array(
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    92
        'success' => false,
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    93
        'error' => 'malformed_response'
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    94
      );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    95
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    96
  $response = array();
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    97
  foreach ( $matches[0] as $i => $_ )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    98
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
    99
    $response[$matches[1][$i]] = $matches[2][$i];
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   100
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   101
  // make sure we have a status
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   102
  if ( !isset($response['status']) )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   103
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   104
    return array(
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   105
        'success' => false,
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   106
        'error' => 'response_missing_status'
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   107
      );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   108
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   109
  // verify response signature
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   110
  // MISSING_PARAMETER is the ONLY situation under which an unsigned response is acceptable
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   111
  if ( $response['status'] !== 'MISSING_PARAMETER' )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   112
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   113
    if ( !isset($response['h']) )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   114
    {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   115
      return array(
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   116
          'success' => false,
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   117
          'error' => 'response_missing_sig'
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   118
        );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   119
    }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   120
    if ( yubikey_sign($response) !== $response['h'] )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   121
    {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   122
      return array(
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   123
          'success' => false,
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   124
          'error' => 'response_invalid_sig'
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   125
        );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   126
    }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   127
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   128
  if ( $response['status'] === 'OK' )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   129
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   130
    return array(
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   131
        'success' => true
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   132
      );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   133
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   134
  else
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   135
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   136
    return array(
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   137
        'success' => false,
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   138
        'error' => strtolower("response_{$response['status']}")
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   139
      );
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   140
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   141
}
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   142
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   143
function yubikey_sign($arr)
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   144
{
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   145
  static $api_key = false;
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   146
  
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   147
  ksort($arr);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   148
  if ( isset($arr['h']) )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   149
    unset($arr['h']);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   150
  
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   151
  if ( !$api_key )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   152
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   153
    $api_key = getConfig('yubikey_api_key');
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   154
    $api_key = hexencode(base64_decode($api_key), '', '');
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   155
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   156
  
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   157
  $req = array();
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   158
  foreach ( $arr as $key => $val )
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   159
  {
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   160
    $req[] = "$key=$val";
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   161
  }
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   162
  $req = implode('&', $req);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   163
  
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   164
  $sig = hmac_sha1($req, $api_key);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   165
  $sig = hexdecode($sig);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   166
  $sig = base64_encode($sig);
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   167
  
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   168
  return $sig;
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   169
}
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   170
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   171
$plugins->attachHook('compile_template', 'yubikey_attach_headers($this);');
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   172
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   173
function yubikey_attach_headers(&$template)
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   174
{
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   175
  $template->add_header('<script type="text/javascript" src="' . scriptPath . '/plugins/yubikey/yubikey.js"></script>');
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   176
  $template->add_header('<link rel="stylesheet" type="text/css" href="' . scriptPath . '/plugins/yubikey/yubikey.css" />');
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   177
}
9d2c4f04a0d0 First commit! Hoping everything works.
Dan
parents:
diff changeset
   178