Added counter and ANY_CLIENT settings to ShowAESKey; Significant improvements to claim system: Added master switch for the whole system; Added ability for administrators to "su" to client ID 0 to manage pooled keys; Added ability for admins to release key when it is added
--- a/YubikeyManagement.php Sat Aug 01 01:42:21 2009 -0400
+++ b/YubikeyManagement.php Mon Aug 03 02:52:59 2009 -0400
@@ -90,8 +90,11 @@
Validation API URL: <span class="yms-copyfield">%validate_url%</span></p>
<p><b>Remember to secure your user account!</b> Your Enano login is used to administer your YMS account. For maximum security, use the Yubikey Settings page of the User Control Panel to require both a password and a Yubikey OTP to log in.</p>',
msg_no_yubikeys: 'No Yubikeys found',
+ msg_editing_zero: '<b>Notice:</b> You are currently viewing the YMS profile for Client ID 0, the pool of claimable keys. By default, anybody can validate or claim these Yubikeys, but you can prevent validation of these keys by marking them inactive here. All key settings such as lifecycle state and notes are reset when a user claims a key here.',
btn_add_key: 'Add Yubikey',
btn_add_key_preregistered: 'Claim a New Key',
+ btn_switch_to_zero: 'Edit claimable pool',
+ btn_switch_from_zero: 'Switch back to my client',
state_active: 'Active',
state_inactive: 'Inactive',
@@ -115,6 +118,9 @@
lbl_addkey_field_any_client_name: 'Allow validation by any client:',
lbl_addkey_field_any_client_hint: 'If unchecked, OTPs from this Yubikey can only be verified by someone using your client ID. Check this if you plan to use this Yubikey on websites you don\'t control.',
lbl_addkey_field_any_client: 'Other clients can validate OTPs from this key',
+ lbl_addkey_field_allow_claim_name: 'Place key in claimable pool:',
+ lbl_addkey_field_allow_claim_hint: 'After this key is added, YMS will release your ownership of this key so that other users may claim it.',
+ lbl_addkey_field_allow_claim: 'Release this key and allow others to claim it',
btn_addkey_submit: 'Register key',
msg_addkey_success: 'This key has been successfully registered.',
@@ -130,10 +136,19 @@
lbl_custom_hint: 'For your security, this is used to validate your ownership of this Yubikey.',
// AES key view interface
+ showaes_heading_main: 'View AES key and counters',
showaes_th: 'AES secret key for key %public_id%',
showaes_lbl_hex: 'Hex:',
showaes_lbl_modhex: 'ModHex:',
showaes_lbl_base64: 'Base64:',
+ showaes_th_counter: 'Counters',
+ showaes_field_session_count: 'Session count:',
+ showaes_field_session_count_hint: 'Incremented by 1 each time you insert this Yubikey into a USB port.',
+ showaes_field_otp_count: 'OTP count:',
+ showaes_field_otp_count_hint: 'Incremented by 1 each time you press the button on the Yubikey; reset when the Yubikey is plugged in.',
+
+ err_expected_int: 'Expected an integer',
+ msg_counter_update_success: 'The counters for this Yubikey have been updated.',
// API key view interface
th_client_id: 'Client ID',
@@ -161,7 +176,7 @@
btn_note_view: 'View or edit note',
btn_note_create: 'No note; click to create',
btn_delete_key: 'Delete key',
- btn_show_aes: 'Show AES secret',
+ btn_show_aes: 'AES secret and counter information',
btn_show_converter: 'Binary encoding converter',
btn_show_client_info: 'View client info',
@@ -172,6 +187,11 @@
acp_field_require_reauth_title: 'Require re-authentication to access YMS interface:',
acp_field_require_reauth_hint: 'This can be redundant and unnecessary if the sole purpose of your Enano installation is for YMS purposes.',
acp_field_require_reauth: 'YMS pages require re-authentication',
+ acp_field_claim_enable_title: 'Allow users to claim Yubikeys:',
+ acp_field_claim_enable_hint: 'If you plan to program your own Yubikeys and give them to others, enable this to allow them to create YMS accounts and "claim" the keys so they can see AES secrets and control settings on their keys.<br />
+ If you enable this, all Administrators will see an option when adding a new key to put it into the pool of unclaimed keys.<br />
+ To claim a Yubikey, YMS requires users to enter a valid OTP, and optionally, an additional field you may configure below.',
+ acp_field_claim_enable: 'Enable the claim system',
acp_field_claimauth_enable_title: 'Use external authentication when claiming Yubikeys:',
acp_field_claimauth_enable_hint: 'This allows you to require an additional value - for example, the receipt number from the user\'s Yubikey order - when Yubikeys are claimed.',
acp_field_claimauth_enable: 'Require additional field to claim a Yubikey',
--- a/yms/admincp.php Sat Aug 01 01:42:21 2009 -0400
+++ b/yms/admincp.php Mon Aug 03 02:52:59 2009 -0400
@@ -21,6 +21,7 @@
if ( isset($_POST['submit']) )
{
setConfig('yms_require_reauth', isset($_POST['require_reauth']) ? '1' : '0');
+ setConfig('yms_claim_enable', isset($_POST['claim_enable']) ? '1' : '0');
setConfig('yms_claim_auth_enable', isset($_POST['claimauth_enable']) ? '1' : '0');
setConfig('yms_claim_auth_field', $_POST['claimauth_field']);
setConfig('yms_claim_auth_url', $_POST['claimauth_url']);
@@ -55,6 +56,19 @@
<tr>
<td class="row2" style="width: 50%;">
+ <?php echo $lang->get('yms_acp_field_claim_enable_title'); ?><br />
+ <small><?php echo $lang->get('yms_acp_field_claim_enable_hint'); ?></small>
+ </td>
+ <td class="row1" style="width: 50%;">
+ <label>
+ <input type="checkbox" name="claim_enable" <?php if ( getConfig('yms_claim_enable', 0) == 1 ) echo 'checked="checked" '; ?>/>
+ <?php echo $lang->get('yms_acp_field_claim_enable'); ?>
+ </label>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="row2" style="width: 50%;">
<?php echo $lang->get('yms_acp_field_claimauth_enable_title'); ?><br />
<small><?php echo $lang->get('yms_acp_field_claimauth_enable_hint'); ?></small>
</td>
--- a/yms/backend.php Sat Aug 01 01:42:21 2009 -0400
+++ b/yms/backend.php Mon Aug 03 02:52:59 2009 -0400
@@ -5,7 +5,7 @@
global $db, $session, $paths, $template, $plugins; // Common objects
if ( $client_id === false )
- $client_id = $session->user_id;
+ $client_id = $GLOBALS['yms_client_id'];
$key = yms_tobinary($key);
$otp = yms_tobinary($otp);
@@ -66,7 +66,7 @@
global $db, $session, $paths, $template, $plugins; // Common objects
if ( $client_id === false )
- $client_id = $session->user_id;
+ $client_id = $GLOBALS['yms_client_id'];
$otp = yms_tobinary($otp);
@@ -114,7 +114,7 @@
global $db, $session, $paths, $template, $plugins; // Common objects
if ( $client_id === false )
- $client_id = $session->user_id;
+ $client_id = $GLOBALS['yms_client_id'];
$q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yms_yubikeys WHERE id = $id AND client_id = $client_id;");
if ( !$q )
@@ -176,6 +176,31 @@
return true;
}
+function yms_update_counters($id, $scount, $tcount, $client_id = false, $any_client = null)
+{
+ global $db, $session, $paths, $template, $plugins; // Common objects
+
+ if ( !$client_id )
+ $client_id = intval($GLOBALS['yms_client_id']);
+
+ foreach ( array($id, $scount, $tcount, $client_id) as $var )
+ if ( (!is_int($var) && !is_string($var)) || (is_string($var) && !ctype_digit($var)) )
+ return "yms_err_expected_int";
+
+ $any_client_sql = '';
+ if ( is_bool($any_client) )
+ {
+ $operand = $any_client ? "|" : "& ~";
+ $any_client_sql = ", flags = flags " . $operand . YMS_ANY_CLIENT;
+ }
+
+ $q = $db->sql_query('UPDATE ' . table_prefix . "yms_yubikeys SET session_count = {$scount}, token_count = {$tcount}{$any_client_sql} WHERE id = $id AND client_id = $client_id");
+ if ( !$q )
+ $db->_die();
+
+ return true;
+}
+
function yms_get_url($url)
{
require_once(ENANO_ROOT . '/includes/http.php');
@@ -291,10 +316,10 @@
{
return 'NO_SUCH_KEY';
}
- if ( !($flags & YMS_ENABLED) )
- {
- return 'NO_SUCH_KEY';
- }
+ }
+ if ( !($flags & YMS_ENABLED) )
+ {
+ return 'NO_SUCH_KEY';
}
// decode the OTP
--- a/yms/yms.php Sat Aug 01 01:42:21 2009 -0400
+++ b/yms/yms.php Mon Aug 03 02:52:59 2009 -0400
@@ -5,6 +5,9 @@
global $db, $session, $paths, $template, $plugins; // Common objects
global $lang;
global $output;
+ global $yms_client_id;
+
+ $yms_client_id = $session->user_id;
// Require re-auth?
if ( $session->auth_level < USER_LEVEL_CHPREF && getConfig('yms_require_reauth', 1) == 1 )
@@ -18,14 +21,45 @@
die_friendly($lang->get('yms_err_yubikey_plugin_missing_title'), '<p>' . $lang->get('yms_err_yubikey_plugin_missing_body') . '</p>');
}
+ // Client switch allowed?
+ if ( $session->user_level >= USER_LEVEL_ADMIN && getConfig('yms_claim_enable', 0) == 1 )
+ {
+ $on_home = empty($_POST) && !$paths->getParam(0);
+
+ // yes.
+ $configkey = "yms_zeroeditsess_{$session->user_id}";
+ if ( getConfig($configkey, 0) == 1 && !isset($_GET['client_switch']) )
+ {
+ // set to zero
+ $yms_client_id = 0;
+ }
+ else if ( !getConfig($configkey) && isset($_GET['client_switch']) )
+ {
+ // set to zero + update config
+ $yms_client_id = 0;
+ setConfig($configkey, 1);
+ }
+ else if ( getConfig($configkey) && isset($_GET['client_switch']) )
+ {
+ // turning off
+ setConfig($configkey, false);
+ }
+
+ // display a notice
+ if ( $yms_client_id == 0 && $on_home )
+ {
+ $output->add_after_header('<div class="info-box">' . $lang->get('yms_msg_editing_zero') . '</div>');
+ }
+ }
+
// Does the client exist?
- $q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yms_clients WHERE id = {$session->user_id};");
+ $q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yms_clients WHERE id = {$yms_client_id};");
if ( !$q )
$db->_die();
$client_exists = $db->numrows();
$db->free_result();
- if ( !$client_exists )
+ if ( !$client_exists && $yms_client_id > 0 )
{
redirect(makeUrlNS('Special', 'YMSCreateClient'), '', '', 0);
}
@@ -57,10 +91,20 @@
$enabled = $_POST['state'] == 'active';
$any_client = isset($_POST['any_client']);
$notes = $_POST['notes'];
+
+ // Release key?
+ if ( $session->user_level >= USER_LEVEL_ADMIN && getConfig('yms_claim_enable', 0) == 1 && isset($_POST['allow_claim']) )
+ {
+ $client_id = 0;
+ // also allow anyone to validate OTPs from it and mark it as active
+ $any_client = true;
+ $enabled = true;
+ }
+
$result = yms_add_yubikey($_POST['add_aes'], $_POST['add_otp'], $client_id, $enabled, $any_client, $notes);
yms_send_response('yms_msg_addkey_success', $result);
}
- else if ( isset($_POST['claim_otp']) )
+ else if ( isset($_POST['claim_otp']) && getConfig('yms_claim_enable', 0) == 1 )
{
// do we need to validate a custom field?
if ( ($url = getConfig('yms_claim_auth_url')) && getConfig('yms_claim_auth_field') && getConfig('yms_claim_auth_enable', 0) == 1 )
@@ -89,6 +133,15 @@
$result = yms_delete_key($id);
yms_send_response('yms_msg_delete_success', $result);
}
+ else if ( isset($_POST['update_counters']) )
+ {
+ $yk_id = $_POST['update_counters'];
+ $scount = $_POST['session_count'];
+ $tcount = $_POST['token_count'];
+ $any_client = isset($_POST['any_client']);
+ $result = yms_update_counters($yk_id, $scount, $tcount, false, $any_client);
+ yms_send_response('yms_msg_counter_update_success', $result);
+ }
if ( isset($_GET['toggle']) && isset($_GET['state']) )
{
@@ -98,7 +151,7 @@
else
$expr = 'flags & ~' . YMS_ENABLED;
- $q = $db->sql_query('UPDATE ' . table_prefix . "yms_yubikeys SET flags = $expr WHERE id = $id AND client_id = {$session->user_id};");
+ $q = $db->sql_query('UPDATE ' . table_prefix . "yms_yubikeys SET flags = $expr WHERE id = $id AND client_id = {$yms_client_id};");
if ( !$q )
$db->die_json();
}
@@ -124,15 +177,17 @@
href="<?php echo makeUrlNS('Special', 'YMS/AddKey'); ?>" onclick="yms_showpage('AddKey'); return false;">
<?php echo $lang->get('yms_btn_add_key'); ?>
</a>
+ <?php if ( getConfig('yms_claim_enable', 0) == 1 && $yms_client_id > 0 ): ?>
<a class="abutton abutton_blue icon" style="background-image: url(<?php echo scriptPath; ?>/plugins/yms/icons/key_add.png);"
href="<?php echo makeUrlNS('Special', 'YMS/AddPreregisteredKey'); ?>" onclick="yms_showpage('AddPreregisteredKey'); return false;">
<?php echo $lang->get('yms_btn_add_key_preregistered'); ?>
</a>
+ <?php endif; ?>
</div>
<?php
// Pull all Yubikeys
- $q = $db->sql_query('SELECT id, public_id, session_count, create_time, access_time, flags, notes FROM ' . table_prefix . "yms_yubikeys WHERE client_id = {$session->user_id} ORDER BY id ASC;");
+ $q = $db->sql_query('SELECT id, public_id, session_count, create_time, access_time, flags, notes FROM ' . table_prefix . "yms_yubikeys WHERE client_id = {$yms_client_id} ORDER BY id ASC;");
if ( !$q )
$db->_die();
@@ -203,7 +258,11 @@
<?php echo $lang->get('yms_btn_show_client_info'); ?>
</a>
- <?php
+ <?php if ( getConfig('yms_claim_enable', 0) == 1 ): ?>
+ <a href="<?php echo makeUrlNS('Special', 'YMS', 'client_switch', true); ?>" class="abutton abutton_green">
+ <?php echo $yms_client_id == 0 ? $lang->get('yms_btn_switch_from_zero') : $lang->get('yms_btn_switch_to_zero'); ?>
+ </a>
+ <?php endif;
}
$db->free_result($q);
@@ -283,6 +342,22 @@
</td>
</tr>
+ <!-- Allow claim -->
+ <?php if ( getConfig('yms_claim_enable', 0) == 1 ): ?>
+ <tr>
+ <td class="row2">
+ <?php echo $lang->get('yms_lbl_addkey_field_allow_claim_name'); ?><br />
+ <small><?php echo $lang->get('yms_lbl_addkey_field_allow_claim_hint'); ?></small>
+ </td>
+ <td class="row1">
+ <label>
+ <input type="checkbox" name="allow_claim" />
+ <?php echo $lang->get('yms_lbl_addkey_field_allow_claim'); ?>
+ </label>
+ </td>
+ </tr>
+ <?php endif; ?>
+
<!-- Notes -->
<tr>
<td class="row2">
@@ -314,6 +389,9 @@
global $db, $session, $paths, $template, $plugins; // Common objects
global $lang, $output;
+ if ( getConfig('yms_claim_enable', 0) != 1 )
+ die();
+
$output->add_after_header('<div class="breadcrumbs">
<a href="' . makeUrlNS('Special', 'YMS') . '">' . $lang->get('yms_specialpage_yms') . '</a> »
' . $lang->get('yms_btn_add_key_preregistered') . '
@@ -406,7 +484,7 @@
function page_Special_YMS_ShowAESKey()
{
global $db, $session, $paths, $template, $plugins; // Common objects
- global $lang, $output;
+ global $lang, $output, $yms_client_id;
$output->add_after_header('<div class="breadcrumbs">
<a href="' . makeUrlNS('Special', 'YMS') . '">' . $lang->get('yms_specialpage_yms') . '</a> »
@@ -416,7 +494,7 @@
$id = intval($paths->getParam(1));
// verify ownership, retrieve key
- $q = $db->sql_query('SELECT client_id, public_id, aes_secret FROM ' . table_prefix . "yms_yubikeys WHERE id = $id;");
+ $q = $db->sql_query('SELECT client_id, public_id, aes_secret, session_count, token_count, flags FROM ' . table_prefix . "yms_yubikeys WHERE id = $id;");
if ( !$q )
$db->_die();
@@ -425,14 +503,20 @@
die_friendly('no rows', '<p>key not found</p>');
}
- list($client_id, $public_id, $secret) = $db->fetchrow_num();
+ list($client_id, $public_id, $secret, $scount, $tcount, $flags) = $db->fetchrow_num();
$db->free_result();
- if ( $client_id !== $session->user_id )
+ if ( $client_id !== $yms_client_id )
die_friendly($lang->get('etc_access_denied_short'), '<p>' . $lang->get('etc_access_denied') . '</p>');
$output->header();
?>
+
+ <h3><?php echo $lang->get('yms_showaes_heading_main'); ?></h3>
+
+ <form action="<?php echo makeUrlNS('Special', 'YMS'); ?>" method="post">
+ <input type="hidden" name="update_counters" value="<?php echo $id; ?>" />
+
<div class="tblholder">
<table border="0" cellspacing="1" cellpadding="4">
<tr>
@@ -471,8 +555,57 @@
</td>
</tr>
+ <!-- COUNTERS -->
+ <tr>
+ <th colspan="2">
+ <?php echo $lang->get('yms_showaes_th_counter'); ?>
+ </th>
+ </tr>
+
+ <tr>
+ <td class="row2">
+ <?php echo $lang->get('yms_showaes_field_session_count'); ?><br />
+ <small><?php echo $lang->get('yms_showaes_field_session_count_hint'); ?></small>
+ </td>
+ <td class="row1">
+ <input type="text" name="session_count" value="<?php echo $scount; ?>" size="5" />
+ </td>
+ </tr>
+
+ <tr>
+ <td class="row2">
+ <?php echo $lang->get('yms_showaes_field_otp_count'); ?><br />
+ <small><?php echo $lang->get('yms_showaes_field_otp_count_hint'); ?></small>
+ </td>
+ <td class="row1">
+ <input type="text" name="token_count" value="<?php echo $tcount; ?>" size="5" />
+ </td>
+ </tr>
+
+ <!-- Any client -->
+ <tr>
+ <td class="row2">
+ <?php echo $lang->get('yms_lbl_addkey_field_any_client_name'); ?><br />
+ <small><?php echo $lang->get('yms_lbl_addkey_field_any_client_hint'); ?></small>
+ </td>
+ <td class="row1">
+ <label>
+ <input type="checkbox" name="any_client" <?php if ( $flags & YMS_ANY_CLIENT ) echo 'checked="checked" '; ?>/>
+ <?php echo $lang->get('yms_lbl_addkey_field_any_client'); ?>
+ </label>
+ </td>
+ </tr>
+
+ <tr>
+ <th class="subhead" colspan="2">
+ <input type="submit" value="<?php echo $lang->get('etc_save_changes'); ?>" />
+ </td>
+ </tr>
+
</table>
</div>
+
+ </form>
<?php
$output->footer();
}
@@ -481,14 +614,14 @@
function page_Special_YMS_ShowClientInfo()
{
global $db, $session, $paths, $template, $plugins; // Common objects
- global $lang, $output;
+ global $lang, $output, $yms_client_id;
$output->add_after_header('<div class="breadcrumbs">
<a href="' . makeUrlNS('Special', 'YMS') . '">' . $lang->get('yms_specialpage_yms') . '</a> »
' . $lang->get('yms_btn_show_client_info') . '
</div>');
- $q = $db->sql_query('SELECT apikey FROM ' . table_prefix . "yms_clients WHERE id = {$session->user_id};");
+ $q = $db->sql_query('SELECT apikey FROM ' . table_prefix . "yms_clients WHERE id = {$yms_client_id};");
if ( !$q )
$db->_die();
@@ -508,7 +641,7 @@
<tr>
<td class="row2"><?php echo $lang->get('yms_lbl_client_id'); ?></td>
- <td class="row1"><?php echo strval($session->user_id); ?></td>
+ <td class="row1"><?php echo strval($yms_client_id); ?></td>
</tr>
<tr>
@@ -696,6 +829,7 @@
function page_Special_YMS_AjaxToggleState()
{
global $db, $session, $paths, $template, $plugins; // Common objects
+ global $yms_client_id;
$id = intval($_POST['id']);
if ( $_POST['state'] === 'active' )
@@ -703,7 +837,7 @@
else
$expr = 'flags & ~' . YMS_ENABLED;
- $q = $db->sql_query('UPDATE ' . table_prefix . "yms_yubikeys SET flags = $expr WHERE id = $id AND client_id = {$session->user_id};");
+ $q = $db->sql_query('UPDATE ' . table_prefix . "yms_yubikeys SET flags = $expr WHERE id = $id AND client_id = {$yms_client_id};");
if ( !$q )
$db->die_json();
@@ -716,11 +850,12 @@
function page_Special_YMS_AjaxNotes()
{
global $db, $session, $paths, $template, $plugins; // Common objects
+ global $yms_client_id;
if ( isset($_POST['get']) )
{
$id = intval($_POST['get']);
- $q = $db->sql_query('SELECT notes FROM ' . table_prefix . "yms_yubikeys WHERE id = $id AND client_id = {$session->user_id};");
+ $q = $db->sql_query('SELECT notes FROM ' . table_prefix . "yms_yubikeys WHERE id = $id AND client_id = {$yms_client_id};");
if ( !$q )
$db->_die();
if ( $db->numrows() < 1 )
@@ -739,7 +874,7 @@
$id = intval($_POST['save']);
$note = trim($_POST['note']);
$note = $db->escape($note);
- $q = $db->sql_query('UPDATE ' . table_prefix . "yms_yubikeys SET notes = '$note' WHERE id = $id AND client_id = {$session->user_id};");
+ $q = $db->sql_query('UPDATE ' . table_prefix . "yms_yubikeys SET notes = '$note' WHERE id = $id AND client_id = {$yms_client_id};");
if ( !$q )
$db->die_json();
@@ -756,6 +891,9 @@
global $db, $session, $paths, $template, $plugins; // Common objects
global $lang;
global $output;
+ global $yms_client_id;
+
+ $yms_client_id = $session->user_id;
// Require re-auth?
if ( $session->auth_level < USER_LEVEL_CHPREF && getConfig('yms_require_reauth', 1) == 1 )
@@ -770,7 +908,7 @@
}
// Does the client exist?
- $q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yms_clients WHERE id = {$session->user_id};");
+ $q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yms_clients WHERE id = {$yms_client_id};");
if ( !$q )
$db->_die();
@@ -790,7 +928,7 @@
// register the client
// SHA1 key length: 160 bits
$api_key = base64_encode(AESCrypt::randkey(160 / 8));
- $client_id = $session->user_id;
+ $client_id = $yms_client_id;
$q = $db->sql_query('INSERT INTO ' . table_prefix . "yms_clients(id, apikey) VALUES ($client_id, '$api_key');");
if ( !$q )