Con Kolivas
9 years ago
26 changed files with 1631 additions and 269 deletions
@ -0,0 +1,121 @@
|
||||
<?php |
||||
# |
||||
function set_2fa($data, $user, $tfa, $ans, $err) |
||||
{ |
||||
$pg = '<h1>Two Factor Authentication Settings</h1>'; |
||||
|
||||
if ($err !== null and $err != '') |
||||
$pg .= "<span class=err>$err<br><br></span>"; |
||||
|
||||
$pg .= '<table cellpadding=20 cellspacing=0 border=1>'; |
||||
$pg .= '<tr class=dc><td><center>'; |
||||
|
||||
$pg .= makeForm('2fa'); |
||||
$pg .= '<table cellpadding=5 cellspacing=0 border=0>'; |
||||
$pg .= '<tr class=dc><td>'; |
||||
switch ($tfa) |
||||
{ |
||||
case '': |
||||
$pg .= '<tr class=dl><td>'; |
||||
$pg .= "You don't have 2FA setup yet<br><br>"; |
||||
$pg .= 'To use 2FA you need an App on your phone/tablet<br>'; |
||||
$pg .= 'The free and recommended ones that have been tested here are:<br><br>'; |
||||
$pg .= "Android: Google Play 'FreeOTP Authenticator' by Red Hat<br>"; |
||||
$pg .= "Apple: App Store 'OTP Auth' by Roland Moers<br><br>"; |
||||
$pg .= 'Click here to start setting up 2FA: '; |
||||
$pg .= '<input type=submit name=Setup value=Setup>'; |
||||
$pg .= '</td></tr>'; |
||||
break; |
||||
case 'test': |
||||
$pg .= '<tr class=dc><td>'; |
||||
$pg .= '2FA is not yet enabled.<br>'; |
||||
$pg .= 'Your 2FA key has been created but needs testing.<br><br>'; |
||||
if (isset($ans['2fa_key'])) |
||||
{ |
||||
$key = $ans['2fa_key']; |
||||
$sfainfo = $ans['2fa_issuer'].': '.$ans['2fa_auth'].' '. |
||||
$ans['2fa_hash'].' '.$ans['2fa_time'].'s'; |
||||
$who = substr($user, 0, 8); |
||||
$sfaurl = 'otpauth://'.$ans['2fa_auth'].'/'.$ans['2fa_issuer']. |
||||
':'.htmlspecialchars($who).'?secret='.$ans['2fa_key']. |
||||
'&algorithm='.$ans['2fa_hash'].'&issuer='.$ans['2fa_issuer']; |
||||
} |
||||
else |
||||
{ |
||||
$key = 'unavailable'; |
||||
$sfainfo = 'unavailable'; |
||||
$sfaurl = 'unavailable'; |
||||
} |
||||
$pg .= "Your 2FA Secret Key is: $key<br>"; |
||||
$pg .= "2FA Settings are $sfainfo<br><br>"; |
||||
$pg .= "2FA URL is <a href='$sfaurl'>Click</a><br><br>"; |
||||
$pg .= '2FA Value: <input name=Value value="" size=10> '; |
||||
$pg .= '<input type=submit name=Test value=Test>'; |
||||
$pg .= '</td></tr>'; |
||||
break; |
||||
case 'ok': |
||||
$pg .= '<tr class=dc><td>'; |
||||
$pg .= '2FA is enabled on your account.<br><br>'; |
||||
$pg .= 'If you wish to replace your Secret Key with a new one:<br><br>'; |
||||
$pg .= 'Current 2FA Value: <input name=Value value="" size=10> '; |
||||
$pg .= '<input type=submit name=New value=New><span class=st1>*</span><br><br>'; |
||||
$pg .= '<span class=st1>*</span>WARNING: replacing the Secret Key will disable 2FA<br>'; |
||||
$pg .= 'until you successfully test the new key.<br><br>'; |
||||
$pg .= '</td></tr>'; |
||||
break; |
||||
} |
||||
|
||||
$pg .= '</table></form>'; |
||||
|
||||
$pg .= '</center></td></tr>'; |
||||
$pg .= '</table>'; |
||||
|
||||
return $pg; |
||||
} |
||||
# |
||||
function do2fa($data, $user) |
||||
{ |
||||
$err = ''; |
||||
$setup = getparam('Setup', false); |
||||
if ($setup === 'Setup') |
||||
{ |
||||
// rand() included as part of the entropy |
||||
$ans = get2fa($user, 'setup', rand(1073741824,2147483647), 0); |
||||
} |
||||
else |
||||
{ |
||||
$value = getparam('Value', false); |
||||
|
||||
$test = getparam('Test', false); |
||||
if ($test === 'Test' and $value !== null) |
||||
$ans = get2fa($user, 'test', 0, $value); |
||||
else |
||||
{ |
||||
$nw = getparam('New', false); |
||||
if ($nw === 'New' and $value !== null) |
||||
$ans = get2fa($user, 'new', rand(1073741824,2147483647), $value); |
||||
else |
||||
$ans = get2fa($user, '', 0, 0); |
||||
} |
||||
} |
||||
if ($ans['STATUS'] != 'ok') |
||||
$err = 'DBERR'; |
||||
else |
||||
{ |
||||
if (isset($ans['2fa_error'])) |
||||
$err = $ans['2fa_error']; |
||||
} |
||||
if (!isset($ans['2fa_status'])) |
||||
$tfa = null; |
||||
else |
||||
$tfa = $ans['2fa_status']; |
||||
$pg = set_2fa($data, $user, $tfa, $ans, $err); |
||||
return $pg; |
||||
} |
||||
# |
||||
function show_2fa($info, $page, $menu, $name, $user) |
||||
{ |
||||
gopage($info, NULL, 'do2fa', $page, $menu, $name, $user); |
||||
} |
||||
# |
||||
?> |
@ -0,0 +1,38 @@
|
||||
SET SESSION AUTHORIZATION 'postgres'; |
||||
|
||||
BEGIN transaction; |
||||
|
||||
DO $$ |
||||
DECLARE ver TEXT; |
||||
BEGIN |
||||
|
||||
UPDATE version set version='1.0.1' where vlock=1 and version='1.0.0'; |
||||
|
||||
IF found THEN |
||||
RETURN; |
||||
END IF; |
||||
|
||||
SELECT version into ver from version |
||||
WHERE vlock=1; |
||||
|
||||
RAISE EXCEPTION 'Wrong DB version - expect "1.0.0" - found "%"', ver; |
||||
|
||||
END $$; |
||||
|
||||
ALTER TABLE ONLY users |
||||
ADD COLUMN userdata text DEFAULT ''::text NOT NULL, |
||||
ADD COLUMN userbits bigint NOT NULL DEFAULT 0; |
||||
|
||||
ALTER TABLE ONLY users |
||||
ALTER COLUMN userbits DROP DEFAULT; |
||||
|
||||
-- match based on ckdb_data.c like_address() |
||||
UPDATE users set userbits=1 where username ~ '[13][A-HJ-NP-Za-km-z1-9]{15,}'; |
||||
|
||||
ALTER TABLE ONLY workers |
||||
ADD COLUMN workerbits bigint NOT NULL DEFAULT 0; |
||||
|
||||
ALTER TABLE ONLY workers |
||||
ALTER COLUMN workerbits DROP DEFAULT; |
||||
|
||||
END transaction; |
@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2015 Andrew Smith |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it |
||||
* under the terms of the GNU General Public License as published by the Free |
||||
* Software Foundation; either version 3 of the License, or (at your option) |
||||
* any later version. See COPYING for more details. |
||||
*/ |
||||
|
||||
#include <openssl/x509.h> |
||||
#include <openssl/hmac.h> |
||||
#include "ckdb.h" |
||||
|
||||
#if (SHA256SIZBIN != SHA256_DIGEST_LENGTH) |
||||
#error "SHA256SIZBIN must = OpenSSL SHA256_DIGEST_LENGTH" |
||||
#endif |
||||
|
||||
static char b32code[] = { |
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', |
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', |
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', |
||||
'Y', 'Z', '2', '3', '4', '5', '6', '7' |
||||
}; |
||||
|
||||
#define ASSERT32(condition) __maybe_unused static char b32code_length_must_be_32[(condition)?1:-1] |
||||
ASSERT32(sizeof(b32code) == 32); |
||||
|
||||
// bin is bigendian, return buf is bigendian
|
||||
char *_tob32(USERS *users, unsigned char *bin, size_t len, char *name, |
||||
size_t olen, WHERE_FFL_ARGS) |
||||
{ |
||||
size_t osiz = (len * 8 + 4) / 5; |
||||
char *buf, *ptr, *st = NULL; |
||||
int i, j, bits, ch; |
||||
|
||||
if (osiz != olen) { |
||||
LOGEMERG("%s() of '%s' data for '%s' invalid olen=%d != osiz=%d" |
||||
WHERE_FFL, |
||||
__func__, name, safe_text_nonull(users->username), |
||||
(int)olen, (int)osiz, WHERE_FFL_PASS); |
||||
FREENULL(st); |
||||
olen = osiz; |
||||
} |
||||
|
||||
buf = malloc(olen+1); |
||||
ptr = buf + olen; |
||||
*ptr = '\0'; |
||||
i = 0; |
||||
while (--ptr >= buf) { |
||||
j = i / 8; |
||||
bits = (31 << (i % 8)); |
||||
ch = (bin[len-j-1] & bits) >> (i % 8); |
||||
if (bits > 255) |
||||
ch |= (bin[len-j-2] & (bits >> 8)) << (8 - (i % 8)); |
||||
// Shouldn't ever happen
|
||||
if (ch < 0 || ch > 31) { |
||||
char *binstr = bin2hex(bin, len); |
||||
LOGEMERG("%s() failure of '%s' data for '%s' invalid " |
||||
"ch=%d, i=%d j=%d bits=%d bin=0x%s len=%d " |
||||
"olen=%d" WHERE_FFL, |
||||
__func__, name, |
||||
safe_text_nonull(users->username), ch, i, j, |
||||
bits, binstr, (int)len, (int)olen, |
||||
WHERE_FFL_PASS); |
||||
FREENULL(st); |
||||
FREENULL(binstr); |
||||
ch = 0; |
||||
} |
||||
*ptr = b32code[ch]; |
||||
i += 5; |
||||
} |
||||
return buf; |
||||
} |
||||
|
||||
bool gen_data(__maybe_unused USERS *users, unsigned char *buf, size_t len, |
||||
int32_t entropy) |
||||
{ |
||||
unsigned char *ptr; |
||||
ssize_t ret, want, got; |
||||
int i; |
||||
|
||||
int fil = open("/dev/random", O_RDONLY); |
||||
if (fil == -1) |
||||
return false; |
||||
|
||||
want = (ssize_t)len; |
||||
got = 0; |
||||
while (got < want) { |
||||
ret = read(fil, buf+got, want-got); |
||||
if (ret < 0) { |
||||
close(fil); |
||||
return false; |
||||
} |
||||
got += ret; |
||||
} |
||||
close(fil); |
||||
|
||||
ptr = (unsigned char *)&entropy; |
||||
for (i = 0; i < (int)sizeof(entropy) && (i + sizeof(entropy)) < len; i++) |
||||
buf[i+sizeof(entropy)] ^= *(ptr + i); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
K_ITEM *gen_2fa_key(K_ITEM *old_u_item, int32_t entropy, char *by, char *code, |
||||
char *inet, tv_t *cd, K_TREE *trf_root) |
||||
{ |
||||
unsigned char key[TOTPAUTH_KEYSIZE]; |
||||
K_ITEM *u_item = NULL; |
||||
USERS *old_users, *users; |
||||
bool ok; |
||||
|
||||
DATA_USERS(old_users, old_u_item); |
||||
ok = gen_data(old_users, key, sizeof(key), entropy); |
||||
if (ok) { |
||||
K_WLOCK(users_free); |
||||
u_item = k_unlink_head(users_free); |
||||
K_WUNLOCK(users_free); |
||||
DATA_USERS(users, u_item); |
||||
memcpy(users, old_users, sizeof(*users)); |
||||
if (users->userdata != EMPTY) { |
||||
users->userdata = strdup(users->userdata); |
||||
if (!users->userdata) |
||||
quithere(1, "strdup OOM"); |
||||
} |
||||
users_userdata_add_bin(users, USER_TOTPAUTH_NAME, |
||||
USER_TOTPAUTH, key, sizeof(key)); |
||||
users_userdata_add_txt(users, USER_TEST2FA_NAME, |
||||
USER_TEST2FA, "Y"); |
||||
ok = users_replace(NULL, u_item, old_u_item, by, code, inet, cd, |
||||
trf_root); |
||||
if (!ok) { |
||||
// u_item was cleaned up in user_replace()
|
||||
u_item = NULL; |
||||
} |
||||
} |
||||
return u_item; |
||||
} |
||||
|
||||
bool check_2fa(USERS *users, int32_t value) |
||||
{ |
||||
char *st = NULL, *tmp1 = NULL, *tmp2 = NULL, *tmp3 = NULL; |
||||
unsigned char tim[sizeof(int64_t)], *bin, *hash; |
||||
unsigned int reslen; |
||||
size_t binlen; |
||||
HMAC_CTX ctx; |
||||
int64_t now; |
||||
int32_t otp; |
||||
int i, offset; |
||||
|
||||
now = (int64_t)time(NULL) / TOTPAUTH_TIME; |
||||
bin = users_userdata_get_bin(users, USER_TOTPAUTH_NAME, |
||||
USER_TOTPAUTH, &binlen); |
||||
if (binlen != TOTPAUTH_KEYSIZE) { |
||||
LOGERR("%s() invalid key for '%s/%s " |
||||
"len(%d) != %d", |
||||
__func__, |
||||
st = safe_text_nonull(users->username), |
||||
USER_TOTPAUTH_NAME, (int)binlen, |
||||
TOTPAUTH_KEYSIZE); |
||||
FREENULL(st); |
||||
return false; |
||||
} |
||||
for (i = 0; i < (int)sizeof(int64_t); i++) |
||||
tim[i] = (now >> 8 * ((sizeof(int64_t) - 1) - i)) & 0xff; |
||||
|
||||
LOGDEBUG("%s() '%s/%s tim=%"PRId64"=%s key=%s=%s", __func__, |
||||
st = safe_text_nonull(users->username), |
||||
USER_TOTPAUTH_NAME, now, |
||||
tmp1 = (char *)bin2hex(&tim, sizeof(tim)), |
||||
tmp2 = (char *)bin2hex(bin, TOTPAUTH_KEYSIZE), |
||||
tmp3 = tob32(users, bin, binlen,USER_TOTPAUTH_NAME, TOTPAUTH_DSP_KEYSIZE)); |
||||
FREENULL(tmp3); |
||||
FREENULL(tmp2); |
||||
FREENULL(tmp1); |
||||
FREENULL(st); |
||||
|
||||
hash = malloc(SHA256_DIGEST_LENGTH); |
||||
if (!hash) |
||||
quithere(1, "malloc OOM"); |
||||
|
||||
HMAC_CTX_init(&ctx); |
||||
HMAC_Init_ex(&ctx, bin, binlen, EVP_sha256(), NULL); |
||||
HMAC_Update(&ctx, (unsigned char *)&tim, sizeof(tim)); |
||||
HMAC_Final(&ctx, hash, &reslen); |
||||
|
||||
LOGDEBUG("%s() '%s/%s hash=%s", __func__, |
||||
st = safe_text_nonull(users->username), |
||||
USER_TOTPAUTH_NAME, |
||||
tmp1 = (char *)bin2hex(hash, SHA256_DIGEST_LENGTH)); |
||||
FREENULL(tmp1); |
||||
FREENULL(st); |
||||
|
||||
offset = hash[reslen-1] & 0xf; |
||||
|
||||
otp = ((hash[offset] & 0x7f) << 24) | ((hash[offset+1] & 0xff) << 16) | |
||||
((hash[offset+2] & 0xff) << 8) | (hash[offset+3] & 0xff); |
||||
|
||||
otp %= 1000000; |
||||
|
||||
LOGDEBUG("%s() '%s/%s offset=%d otp=%"PRId32" value=%"PRId32, |
||||
__func__, st = safe_text_nonull(users->username), |
||||
USER_TOTPAUTH_NAME, offset, otp, value); |
||||
FREENULL(st); |
||||
FREENULL(hash); |
||||
|
||||
if (otp == value) |
||||
return true; |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
bool tst_2fa(K_ITEM *old_u_item, int32_t value, char *by, char *code, |
||||
char *inet, tv_t *cd, K_TREE *trf_root) |
||||
{ |
||||
K_ITEM *u_item; |
||||
USERS *old_users, *users; |
||||
bool ok; |
||||
|
||||
DATA_USERS(old_users, old_u_item); |
||||
ok = check_2fa(old_users, value); |
||||
if (ok) { |
||||
K_WLOCK(users_free); |
||||
u_item = k_unlink_head(users_free); |
||||
K_WUNLOCK(users_free); |
||||
DATA_USERS(users, u_item); |
||||
memcpy(users, old_users, sizeof(*users)); |
||||
if (users->userdata != EMPTY) { |
||||
users->userdata = strdup(users->userdata); |
||||
if (!users->userdata) |
||||
quithere(1, "strdup OOM"); |
||||
} |
||||
users_userdata_del(users, USER_TEST2FA_NAME, USER_TEST2FA); |
||||
ok = users_replace(NULL, u_item, old_u_item, by, code, inet, cd, |
||||
trf_root); |
||||
// if !ok : u_item was cleaned up in user_replace()
|
||||
} |
||||
return ok; |
||||
} |
Loading…
Reference in new issue