author | Dan |
Mon, 09 Nov 2009 09:21:05 -0500 | |
changeset 33 | 1303cf9c594c |
parent 32 | b00055a88867 |
child 34 | 6e947fa21237 |
permissions | -rw-r--r-- |
0 | 1 |
<?php |
2 |
||
3
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
3 |
if ( getConfig('yubikey_enable', '1') != '1' ) |
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
4 |
return true; |
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
5 |
|
0 | 6 |
// hook into auth |
7 |
$plugins->attachHook('login_process_userdata_json', 'return yubikey_auth_hook_json($userinfo, $req["level"], @$req["remember"]);'); |
|
8 |
// hook into special page init |
|
9 |
$plugins->attachHook('session_started', 'yubikey_add_special_pages();'); |
|
32 | 10 |
// session key security |
11 |
$plugins->attachHook('session_key_calc', 'yubikey_sk_calc($user_id, $key_pieces, $sk_mode);'); |
|
0 | 12 |
|
13 |
function yubikey_auth_hook_json(&$userdata, $level, $remember) |
|
14 |
{ |
|
15 |
global $db, $session, $paths, $template, $plugins; // Common objects |
|
16 |
global $lang; |
|
17 |
||
18 |
$do_validate_otp = false; |
|
19 |
$do_validate_user = false; |
|
20 |
$do_validate_pass = false; |
|
21 |
||
22 |
$user_flag = ( $level >= USER_LEVEL_CHPREF ) ? YK_SEC_ELEV_USERNAME : YK_SEC_NORMAL_USERNAME; |
|
23 |
$pass_flag = ( $level >= USER_LEVEL_CHPREF ) ? YK_SEC_ELEV_PASSWORD : YK_SEC_NORMAL_PASSWORD; |
|
24 |
||
25 |
$auth_log_prefix = ( $level >= USER_LEVEL_CHPREF ) ? 'admin_' : ''; |
|
26 |
||
2
6edc6ebb3b39
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.
Dan
parents:
0
diff
changeset
|
27 |
// Sort of a hack: if the password looks like an OTP and the OTP field is empty, use the password as the OTP |
6edc6ebb3b39
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.
Dan
parents:
0
diff
changeset
|
28 |
if ( empty($userdata['yubikey_otp']) && preg_match('/^[cbdefghijklnrtuv]{44}$/', $userdata['password'] ) ) |
6edc6ebb3b39
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.
Dan
parents:
0
diff
changeset
|
29 |
{ |
6edc6ebb3b39
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.
Dan
parents:
0
diff
changeset
|
30 |
$userdata['yubikey_otp'] = $userdata['password']; |
6edc6ebb3b39
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.
Dan
parents:
0
diff
changeset
|
31 |
} |
6edc6ebb3b39
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.
Dan
parents:
0
diff
changeset
|
32 |
|
33
1303cf9c594c
Removed lockout check, as it is now done in the login preprocess layer.
Dan
parents:
32
diff
changeset
|
33 |
// Lockouts removed from here - they're done during preprocessing now. |
25 | 34 |
|
0 | 35 |
if ( !empty($userdata['username']) ) |
36 |
{ |
|
37 |
// get flags |
|
38 |
$q = $db->sql_query('SELECT user_id, user_yubikey_flags FROM ' . table_prefix . "users WHERE " . ENANO_SQLFUNC_LOWERCASE . "(username) = '" . $db->escape(strtolower($userdata['username'])) . "';"); |
|
39 |
if ( !$q ) |
|
40 |
$db->die_json(); |
|
41 |
||
42 |
if ( $db->numrows() < 1 ) |
|
43 |
{ |
|
44 |
// Username not found - let the main login function handle it |
|
45 |
$db->free_result(); |
|
46 |
return null; |
|
47 |
} |
|
48 |
list($user_id, $flags) = $db->fetchrow_num(); |
|
49 |
$flags = intval($flags); |
|
50 |
// At the point the username is validated. |
|
51 |
$do_validate_user = false; |
|
52 |
$do_validate_pass = $flags & $pass_flag; |
|
53 |
if ( empty($userdata['yubikey_otp']) ) |
|
54 |
{ |
|
55 |
// no OTP was provided |
|
56 |
// make sure the user has allowed logging in with no OTP |
|
57 |
if ( !($flags & YK_SEC_ALLOW_NO_OTP) ) |
|
58 |
{ |
|
59 |
// We also might have no Yubikeys enrolled. |
|
60 |
$q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yubikey WHERE user_id = $user_id;"); |
|
61 |
if ( !$q ) |
|
62 |
$db->die_json(); |
|
63 |
||
64 |
if ( $db->numrows() > 0 ) |
|
65 |
{ |
|
66 |
// Yep at least one key is enrolled. |
|
67 |
// I don't think these should be logged because they'll usually just be innocent mistakes. |
|
68 |
$db->free_result(); |
|
69 |
return array( |
|
70 |
'mode' => 'error', |
|
71 |
'error' => 'yubiauth_err_must_have_otp' |
|
72 |
); |
|
73 |
} |
|
74 |
// Nope, no keys enrolled, user hasn't enabled Yubikey support |
|
75 |
$db->free_result(); |
|
76 |
} |
|
77 |
// we're ok, use normal password auth |
|
78 |
return null; |
|
79 |
} |
|
80 |
else |
|
81 |
{ |
|
17
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
82 |
// user did enter an OTP; make sure it's associated with the username |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
83 |
$yubi_uid = $db->escape(substr($userdata['yubikey_otp'], 0, 12)); |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
84 |
$q = $db->sql_query('SELECT 1 FROM ' . table_prefix . 'yubikey WHERE yubi_uid = \'' . $yubi_uid . '\';'); |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
85 |
if ( !$q ) |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
86 |
$db->die_json(); |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
87 |
if ( $db->numrows() < 1 ) |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
88 |
{ |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
89 |
$db->free_result(); |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
90 |
return array( |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
91 |
'mode' => 'error', |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
92 |
'error' => 'yubiauth_err_key_not_authorized' |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
93 |
); |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
94 |
} |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
95 |
$db->free_result(); |
0 | 96 |
$do_validate_otp = true; |
97 |
} |
|
98 |
} |
|
99 |
else if ( !empty($userdata['yubikey_otp']) ) |
|
100 |
{ |
|
101 |
// we have an OTP, but no username to work with |
|
102 |
$yubi_uid = substr($userdata['yubikey_otp'], 0, 12); |
|
103 |
if ( !preg_match('/^[cbdefghijklnrtuv]{12}$/', $yubi_uid ) ) |
|
104 |
{ |
|
105 |
return array( |
|
106 |
'mode' => 'error', |
|
107 |
'error' => 'yubiauth_err_invalid_otp' |
|
108 |
); |
|
109 |
} |
|
110 |
$q = $db->sql_query('SELECT u.user_id, u.username, u.user_yubikey_flags FROM ' . table_prefix . "users AS u\n" |
|
111 |
. " LEFT JOIN " . table_prefix . "yubikey AS y\n" |
|
112 |
. " ON ( y.user_id = u.user_id )\n" |
|
113 |
. " WHERE y.yubi_uid = '$yubi_uid'\n" |
|
114 |
. " GROUP BY u.user_yubikey_flags;"); |
|
115 |
if ( !$q ) |
|
116 |
$db->_die(); |
|
117 |
||
118 |
if ( $db->numrows() < 1 ) |
|
119 |
{ |
|
120 |
if ( !$do_validate_pass ) |
|
121 |
$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n" |
|
122 |
. ' (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \'(Yubikey)\', ' |
|
123 |
. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
|
124 |
||
125 |
return array( |
|
126 |
'mode' => 'error', |
|
127 |
'error' => 'yubiauth_err_key_not_authorized' |
|
128 |
); |
|
129 |
} |
|
130 |
||
131 |
list($user_id, $username, $flags) = $db->fetchrow_num(); |
|
132 |
$do_validate_otp = true; |
|
133 |
$do_validate_user = $flags & $user_flag; |
|
134 |
$do_validate_pass = $flags & $pass_flag; |
|
135 |
} |
|
136 |
else |
|
137 |
{ |
|
138 |
// Nothing - no username or OTP. This request can't be used; throw it out. |
|
139 |
return array( |
|
140 |
'mode' => 'error', |
|
141 |
'error' => 'yubiauth_err_nothing_provided' |
|
142 |
); |
|
143 |
} |
|
144 |
if ( $do_validate_otp ) |
|
145 |
{ |
|
146 |
// We need to validate the OTP. |
|
147 |
$otp_check = yubikey_validate_otp($userdata['yubikey_otp']); |
|
148 |
if ( !$otp_check['success'] ) |
|
149 |
{ |
|
150 |
if ( !$do_validate_pass ) |
|
151 |
$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n" |
|
152 |
. ' (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \'(Yubikey)\', ' |
|
153 |
. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
|
17
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
154 |
|
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
155 |
if ( $otp_check['error'] === 'http_failed' ) |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
156 |
{ |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
157 |
return array( |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
158 |
'mode' => 'error', |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
159 |
'error' => 'yubiauth_err_' . $otp_check['error'], |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
160 |
'http_error' => $otp_check['http_error'] |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
161 |
); |
e04c0f64e972
SECURITY (critical): If username provided, any Yubikey could be used to log in.
Dan
parents:
5
diff
changeset
|
162 |
} |
0 | 163 |
return array( |
164 |
'mode' => 'error', |
|
165 |
'error' => 'yubiauth_err_' . $otp_check['error'] |
|
166 |
); |
|
167 |
} |
|
168 |
} |
|
169 |
if ( $do_validate_user ) |
|
170 |
{ |
|
171 |
if ( empty($username) ) |
|
172 |
{ |
|
173 |
return array( |
|
174 |
'mode' => 'error', |
|
175 |
'error' => 'yubiauth_err_must_have_username' |
|
176 |
); |
|
177 |
} |
|
178 |
if ( strtolower($username) !== strtolower($userdata['username']) ) |
|
179 |
{ |
|
180 |
// Username incorrect |
|
181 |
if ( !$do_validate_pass ) |
|
182 |
$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n" |
|
183 |
. ' (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \'(Yubikey)\', ' |
|
184 |
. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
|
185 |
return array( |
|
186 |
'mode' => 'error', |
|
187 |
'error' => 'invalid_credentials' |
|
188 |
); |
|
189 |
} |
|
190 |
} |
|
191 |
// Do we need to have the password validated? |
|
192 |
if ( $do_validate_pass ) |
|
193 |
{ |
|
5 | 194 |
if ( empty($userdata['password']) ) |
195 |
{ |
|
196 |
return array( |
|
197 |
'mode' => 'error', |
|
198 |
'error' => 'yubiauth_err_must_have_password' |
|
199 |
); |
|
200 |
} |
|
0 | 201 |
// Yes; return and let the login API continue |
202 |
return null; |
|
203 |
} |
|
204 |
else |
|
205 |
{ |
|
206 |
// No password required; validated, issue session key |
|
207 |
$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n" |
|
208 |
. ' (\'security\', \'' . $auth_log_prefix . 'auth_good\', '.time().', \''.enano_date('d M Y h:i a').'\', \'' . $db->escape($userdata['username']) . '\', ' |
|
209 |
. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
|
210 |
||
211 |
$q = $db->sql_query('SELECT password FROM ' . table_prefix . "users WHERE user_id = $user_id;"); |
|
212 |
if ( !$q ) |
|
213 |
$db->_die(); |
|
214 |
||
215 |
list($password) = $db->fetchrow_num(); |
|
216 |
$db->free_result(); |
|
217 |
||
218 |
$session->register_session($user_id, $userdata['username'], $password, $level, $remember); |
|
219 |
return true; |
|
220 |
} |
|
221 |
} |
|
222 |
||
223 |
function yubikey_add_special_pages() |
|
224 |
{ |
|
225 |
global $db, $session, $paths, $template, $plugins; // Common objects |
|
226 |
global $lang; |
|
227 |
||
3
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
228 |
if ( getConfig('yubikey_enable', '1') != '1' ) |
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
229 |
return true; |
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
230 |
|
0 | 231 |
$paths->add_page(array( |
232 |
'name' => $lang->get('yubiauth_specialpage_yubikey'), |
|
233 |
'urlname' => 'Yubikey', |
|
234 |
'namespace' => 'Special', |
|
235 |
'visible' => 0, 'protected' => 0, 'comments_on' => 0, 'special' => 0 |
|
236 |
)); |
|
237 |
} |
|
238 |
||
32 | 239 |
function yubikey_sk_calc($user_id, &$key_pieces, &$sk_mode) |
240 |
{ |
|
241 |
global $db, $session, $paths, $template, $plugins; // Common objects |
|
242 |
// hash the user's yubikeys |
|
243 |
$q = $db->sql_query('SELECT yubi_uid FROM ' . table_prefix . "yubikey WHERE user_id = $user_id;"); |
|
244 |
if ( !$q ) |
|
245 |
$db->_die(); |
|
246 |
||
247 |
while ( $row = $db->fetchrow() ) |
|
248 |
{ |
|
249 |
$key_pieces[] = $row['yubi_uid']; |
|
250 |
} |
|
251 |
} |
|
252 |
||
0 | 253 |
function page_Special_Yubikey() |
254 |
{ |
|
255 |
global $db, $session, $paths, $template, $plugins; // Common objects |
|
256 |
||
257 |
header('Content-type: text/javascript'); |
|
258 |
/* |
|
259 |
if ( isset($_GET['validate_otp']) ) |
|
260 |
{ |
|
261 |
echo enano_json_encode(yubikey_validate_otp($_GET['validate_otp'])); |
|
262 |
return true; |
|
263 |
} |
|
264 |
*/ |
|
265 |
if ( isset($_GET['get_flags']) || isset($_POST['get_flags']) ) |
|
266 |
{ |
|
267 |
$yubi_uid = substr($_REQUEST['get_flags'], 0, 12); |
|
268 |
if ( !preg_match('/^[cbdefghijklnrtuv]{12}$/', $yubi_uid) ) |
|
269 |
{ |
|
270 |
return print enano_json_encode(array( |
|
271 |
'mode' => 'error', |
|
272 |
'error' => 'invalid_otp' |
|
273 |
)); |
|
274 |
} |
|
275 |
$q = $db->sql_query('SELECT u.user_yubikey_flags FROM ' . table_prefix . "users AS u\n" |
|
276 |
. " LEFT JOIN " . table_prefix . "yubikey AS y\n" |
|
277 |
. " ON ( y.user_id = u.user_id )\n" |
|
278 |
. " WHERE y.yubi_uid = '$yubi_uid'\n" |
|
279 |
. " GROUP BY u.user_yubikey_flags;"); |
|
280 |
if ( !$q ) |
|
281 |
$db->_die(); |
|
282 |
||
283 |
if ( $db->numrows() < 1 ) |
|
284 |
{ |
|
285 |
return print enano_json_encode(array( |
|
286 |
'mode' => 'error', |
|
287 |
'error' => 'key_not_authorized' |
|
288 |
)); |
|
289 |
} |
|
290 |
||
291 |
list($flags) = $db->fetchrow_num(); |
|
292 |
||
293 |
echo enano_json_encode(array( |
|
294 |
// We strip YK_SEC_ALLOW_NO_OTP here for security reasons. |
|
295 |
'flags' => intval($flags & ~YK_SEC_ALLOW_NO_OTP) |
|
296 |
)); |
|
297 |
||
298 |
return true; |
|
299 |
} |
|
300 |
} |
|
301 |