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