#include "bee2/core/blob.h"
#include "bee2/core/dec.h"
#include "bee2/core/err.h"
#include "bee2/core/mem.h"
#include "bee2/core/str.h"
#include "bee2/core/tm.h"
#include "bee2/core/util.h"
#include "bee2/crypto/belt.h"
#include "bee2/crypto/botp.h"
static const u32 powers_of_10[10] = {
1u,
10u,
100u,
1000u,
10000u,
100000u,
1000000u,
10000000u,
100000000u,
1000000000u,
};
void botpDT(char* otp, size_t digit, const octet mac[], size_t mac_len)
{
register u32 pwd;
register size_t offset;
ASSERT(mac_len >= 20);
ASSERT(4 <= digit && digit <= 9);
ASSERT(memIsValid(otp, digit + 1));
ASSERT(memIsValid(mac, mac_len));
offset = mac[mac_len - 1] & 15;
pwd = mac[offset], pwd <<= 8;
pwd ^= mac[offset + 1], pwd <<= 8;
pwd ^= mac[offset + 2], pwd <<= 8;
pwd ^= mac[offset + 3];
pwd &= 0x7FFFFFFF;
pwd %= powers_of_10[digit];
decFromU32(otp, digit, pwd);
CLEAN2(offset, pwd);
}
static void botpTimeToCtr(octet ctr[8], tm_time_t t)
{
ASSERT(sizeof(t) <= 8);
memSetZero(ctr, 8 - sizeof(t));
memCopy(ctr + 8 - sizeof(t), &t, sizeof(t));
#if (OCTET_ORDER == LITTLE_ENDIAN)
memRev(ctr + 8 - sizeof(t), sizeof(t));
#endif
}
void botpCtrNext(octet ctr[8])
{
register octet carry = 1;
ASSERT(memIsValid(ctr, 8));
carry = ((ctr[7] += carry) < carry);
carry = ((ctr[6] += carry) < carry);
carry = ((ctr[5] += carry) < carry);
carry = ((ctr[4] += carry) < carry);
carry = ((ctr[3] += carry) < carry);
carry = ((ctr[2] += carry) < carry);
carry = ((ctr[1] += carry) < carry);
ctr[0] += carry;
CLEAN(carry);
}
typedef struct
{
size_t digit;
octet ctr[8];
octet ctr1[8];
octet mac[32];
char otp[10];
octet stack[];
} botp_hotp_st;
size_t botpHOTP_keep()
{
return sizeof(botp_hotp_st) + 2 * beltHMAC_keep();
}
void botpHOTPStart(void* state, size_t digit, const octet key[],
size_t key_len)
{
botp_hotp_st* st = (botp_hotp_st*)state;
ASSERT(6 <= digit && digit <= 8);
ASSERT(memIsDisjoint2(key, key_len, state, botpHOTP_keep()));
st->digit = digit;
beltHMACStart(st->stack + beltHMAC_keep(), key, key_len);
}
void botpHOTPStepS(void* state, const octet ctr[8])
{
botp_hotp_st* st = (botp_hotp_st*)state;
ASSERT(memIsDisjoint2(ctr, 8, state, botpHOTP_keep()) || ctr == st->ctr);
memMove(st->ctr, ctr, 8);
}
void botpHOTPStepR(char* otp, void* state)
{
botp_hotp_st* st = (botp_hotp_st*)state;
ASSERT(memIsDisjoint2(otp, st->digit + 1, state, botpHOTP_keep()) ||
otp == st->otp);
memCopy(st->stack, st->stack + beltHMAC_keep(), beltHMAC_keep());
beltHMACStepA(st->ctr, 8, st->stack);
beltHMACStepG(st->mac, st->stack);
botpDT(otp, st->digit, st->mac, 32);
botpCtrNext(st->ctr);
}
bool_t botpHOTPStepV(const char* otp, void* state)
{
botp_hotp_st* st = (botp_hotp_st*)state;
ASSERT(strIsValid(otp));
ASSERT(memIsDisjoint2(otp, strLen(otp) + 1, state, botpHOTP_keep()));
memCopy(st->ctr1, st->ctr, 8);
botpHOTPStepR(st->otp, state);
if (strEq(st->otp, otp))
return TRUE;
memCopy(st->ctr, st->ctr1, 8);
return FALSE;
}
void botpHOTPStepG(octet ctr[8], const void* state)
{
const botp_hotp_st* st = (const botp_hotp_st*)state;
ASSERT(memIsDisjoint2(ctr, 8, state, botpHOTP_keep()) || ctr == st->ctr);
memMove(ctr, st->ctr, 8);
}
err_t botpHOTPRand(char* otp, size_t digit, const octet key[], size_t key_len,
const octet ctr[8])
{
void* state;
if (digit < 6 || digit > 8)
return ERR_BAD_PARAMS;
if (!memIsValid(otp, digit + 1) ||
!memIsValid(key, key_len) ||
!memIsValid(ctr, 8))
return ERR_BAD_INPUT;
state = blobCreate(botpHOTP_keep());
if (state == 0)
return ERR_OUTOFMEMORY;
botpHOTPStart(state, digit, key, key_len);
botpHOTPStepS(state, ctr);
botpHOTPStepR(otp, state);
blobClose(state);
return ERR_OK;
}
err_t botpHOTPVerify(const char* otp, const octet key[], size_t key_len,
const octet ctr[8])
{
void* state;
bool_t success;
if (!strIsValid(otp) || strLen(otp) < 6 || strLen(otp) > 8)
return ERR_BAD_PWD;
if (!memIsValid(key, key_len) || !memIsValid(ctr, 8))
return ERR_BAD_INPUT;
state = blobCreate(botpHOTP_keep());
if (state == 0)
return ERR_OUTOFMEMORY;
botpHOTPStart(state, strLen(otp), key, key_len);
botpHOTPStepS(state, ctr);
success = botpHOTPStepV(otp, state);
blobClose(state);
return success ? ERR_OK : ERR_BAD_PWD;
}
typedef struct
{
size_t digit;
octet t[8];
octet mac[32];
char otp[10];
octet stack[];
} botp_totp_st;
size_t botpTOTP_keep()
{
return sizeof(botp_totp_st) + 2 * beltHMAC_keep();
}
void botpTOTPStart(void* state, size_t digit, const octet key[],
size_t key_len)
{
botp_totp_st* st = (botp_totp_st*)state;
ASSERT(6 <= digit && digit <= 8);
ASSERT(memIsDisjoint2(key, key_len, state, botpTOTP_keep()));
st->digit = digit;
beltHMACStart(st->stack + beltHMAC_keep(), key, key_len);
}
void botpTOTPStepR(char* otp, tm_time_t t, void* state)
{
botp_totp_st* st = (botp_totp_st*)state;
ASSERT(t != TIME_ERR);
ASSERT(memIsDisjoint2(otp, st->digit + 1, state, botpHOTP_keep()) ||
otp == st->otp);
memCopy(st->stack, st->stack + beltHMAC_keep(), beltHMAC_keep());
botpTimeToCtr(st->t, t);
beltHMACStepA(st->t, 8, st->stack);
beltHMACStepG(st->mac, st->stack);
botpDT(otp, st->digit, st->mac, 32);
}
bool_t botpTOTPStepV(const char* otp, tm_time_t t, void* state)
{
botp_totp_st* st = (botp_totp_st*)state;
ASSERT(strIsValid(otp));
ASSERT(t != TIME_ERR);
ASSERT(memIsDisjoint2(otp, strLen(otp) + 1, state, botpTOTP_keep()));
botpTOTPStepR(st->otp, t, state);
return strEq(st->otp, otp);
}
err_t botpTOTPRand(char* otp, size_t digit, const octet key[], size_t key_len,
tm_time_t t)
{
void* state;
if (digit < 6 || digit > 8)
return ERR_BAD_PARAMS;
if (t == TIME_ERR)
return ERR_BAD_TIME;
if (!memIsValid(otp, digit + 1) ||
!memIsValid(key, key_len))
return ERR_BAD_INPUT;
state = blobCreate(botpTOTP_keep());
if (state == 0)
return ERR_OUTOFMEMORY;
botpTOTPStart(state, digit, key, key_len);
botpTOTPStepR(otp, t, state);
blobClose(state);
return ERR_OK;
}
err_t botpTOTPVerify(const char* otp, const octet key[], size_t key_len,
tm_time_t t)
{
void* state;
bool_t success;
if (!strIsValid(otp) || strLen(otp) < 6 || strLen(otp) > 8)
return ERR_BAD_PWD;
if (t == TIME_ERR)
return ERR_BAD_TIME;
if (!memIsValid(key, key_len))
return ERR_BAD_INPUT;
state = blobCreate(botpTOTP_keep());
if (state == 0)
return ERR_OUTOFMEMORY;
botpTOTPStart(state, strLen(otp), key, key_len);
success = botpTOTPStepV(otp, t, state);
blobClose(state);
return success ? ERR_OK : ERR_BAD_PWD;
}
typedef struct botp_ocra_st
{
size_t digit;
octet ctr[8];
octet ctr1[8];
size_t ctr_len;
octet q[128];
char q_type;
size_t q_max;
octet p[64];
size_t p_len;
octet s[512];
size_t s_len;
octet t[8];
tm_time_t ts;
octet mac[32];
char otp[10];
octet stack[];
} botp_ocra_st;
size_t botpOCRA_keep()
{
return sizeof(botp_ocra_st) + 2 * beltHMAC_keep();
}
static const char ocra_prefix[] = "OCRA-1:HOTP-";
static const char ocra_hbelt[] = "HBELT";
static const char ocra_sha1[] = "SHA1";
static const char ocra_sha256[] = "SHA256";
static const char ocra_sha512[] = "SHA512";
bool_t botpOCRAStart(void* state, const char* suite, const octet key[],
size_t key_len)
{
botp_ocra_st* st = (botp_ocra_st*)state;
const char* suite_save = suite;
ASSERT(strIsValid(suite));
ASSERT(memIsDisjoint2(suite, strLen(suite) + 1, state, botpOCRA_keep()));
ASSERT(memIsDisjoint2(key, key_len, state, botpOCRA_keep()));
memSetZero(st, botpOCRA_keep());
if (!strStartsWith(suite, ocra_prefix))
return FALSE;
suite += strLen(ocra_prefix);
if (!strStartsWith(suite, ocra_hbelt))
return FALSE;
suite += strLen(ocra_hbelt);
if (*suite++ != '-')
return FALSE;
if (*suite < '4' || *suite > '9')
return FALSE;
st->digit = (size_t)(*suite++ - '0');
if (*suite++ != ':')
return FALSE;
if (*suite == 'C')
{
if (*++suite != '-')
return FALSE;
++suite;
st->ctr_len = 8;
}
if (*suite++ != 'Q')
return FALSE;
switch (*suite)
{
case 'A':
case 'N':
case 'H':
st->q_type = *suite++;
break;
default:
return FALSE;
}
if (suite[0] < '0' || suite[0] > '9' || suite[1] < '0' || suite[1] > '9')
return FALSE;
st->q_max = (size_t)(suite[0] - '0');
st->q_max *= 10, st->q_max += (size_t)(suite[1] - '0');
if (st->q_max < 4 || st->q_max > 64)
return FALSE;
suite += 2;
if (strStartsWith(suite, "-P"))
{
suite += 2;
if (strStartsWith(suite, ocra_hbelt))
{
suite += strLen(ocra_hbelt);
st->p_len = 32;
}
else if (strStartsWith(suite, ocra_sha1))
{
suite += strLen(ocra_sha1);
st->p_len = 20;
}
else if (strStartsWith(suite, ocra_sha256))
{
suite += strLen(ocra_sha256);
st->p_len = 32;
}
else if (strStartsWith(suite, ocra_sha512))
{
suite += strLen(ocra_sha512);
st->p_len = 64;
}
else
return FALSE;
}
if (strStartsWith(suite, "-S"))
{
suite += 2;
if (suite[0] < '0' || suite[0] > '9' ||
suite[1] < '0' || suite[1] > '9' ||
suite[2] < '0' || suite[2] > '9')
return FALSE;
st->s_len = (size_t)(suite[0] - '0');
st->s_len *= 10, st->s_len += (size_t)(suite[1] - '0');
st->s_len *= 10, st->s_len += (size_t)(suite[2] - '0');
if (st->s_len > 512)
return FALSE;
suite += 3;
}
if (strStartsWith(suite, "-T"))
{
suite += 2;
if (*suite < '1' || *suite > '9')
return FALSE;
st->ts = (size_t)(*suite++ - '0');
if (*suite >= '0' && *suite <= '9')
st->ts *= 10, st->ts += (size_t)(*suite++ - '0');
switch (*suite++)
{
case 'S':
if (st->ts > 59)
return FALSE;
break;
case 'M':
if (st->ts > 59)
return FALSE;
st->ts *= 60;
break;
case 'H':
if (st->ts > 48)
return FALSE;
st->ts *= 3600;
break;
default:
return FALSE;
}
}
if (*suite)
return FALSE;
beltHMACStart(st->stack + beltHMAC_keep(), key, key_len);
beltHMACStepA(suite_save, strLen(suite_save) + 1,
st->stack + beltHMAC_keep());
return TRUE;
}
void botpOCRAStepS(void* state, const octet ctr[8], const octet p[],
const octet s[])
{
botp_ocra_st* st = (botp_ocra_st*)state;
ASSERT(memIsValid(state, botpOCRA_keep()));
if (st->ctr_len)
{
ASSERT(memIsDisjoint2(ctr, 8, st, botpOCRA_keep()) || ctr == st->ctr);
memMove(st->ctr, ctr, 8);
}
if (st->p_len)
{
ASSERT(memIsDisjoint2(p, st->p_len, st, botpOCRA_keep()) || p == st->p);
memMove(st->p, p, st->p_len);
}
if (st->s_len)
{
ASSERT(memIsDisjoint2(p, st->s_len, s, botpOCRA_keep()) || s == st->s);
memMove(st->s, s, st->s_len);
}
}
void botpOCRAStepR(char* otp, const octet q[], size_t q_len, tm_time_t t,
void* state)
{
botp_ocra_st* st = (botp_ocra_st*)state;
ASSERT(memIsDisjoint2(otp, st->digit + 1, state, botpOCRA_keep()) ||
otp == st->otp);
ASSERT(4 <= q_len && q_len <= 2 * st->q_max);
ASSERT(memIsDisjoint2(q, q_len, state, botpOCRA_keep() || q == st->q));
ASSERT(t != TIME_ERR);
memCopy(st->stack, st->stack + beltHMAC_keep(), beltHMAC_keep());
if (st->ctr_len)
beltHMACStepA(st->ctr, 8, st->stack), botpCtrNext(st->ctr);
memMove(st->q, q, q_len);
memSetZero(st->q + q_len, 128 - q_len);
beltHMACStepA(st->q, 128, st->stack);
if (st->p_len)
beltHMACStepA(st->p, st->p_len, st->stack);
if (st->s_len)
beltHMACStepA(st->s, st->s_len, st->stack);
if (st->ts)
botpTimeToCtr(st->t, t), beltHMACStepA(st->t, 8, st->stack);
beltHMACStepG(st->mac, st->stack);
botpDT(otp, st->digit, st->mac, 32);
}
bool_t botpOCRAStepV(const char* otp, const octet q[], size_t q_len,
tm_time_t t, void* state)
{
botp_ocra_st* st = (botp_ocra_st*)state;
ASSERT(strIsValid(otp));
ASSERT(memIsDisjoint2(otp, strLen(otp) + 1, state, botpOCRA_keep()));
memCopy(st->ctr1, st->ctr, 8);
botpOCRAStepR(st->otp, q, q_len, t, state);
if (strEq(st->otp, otp))
return TRUE;
memCopy(st->ctr, st->ctr1, 8);
return FALSE;
}
void botpOCRAStepG(octet ctr[8], const void* state)
{
const botp_ocra_st* st = (const botp_ocra_st*)state;
ASSERT(memIsDisjoint2(ctr, 8, state, botpOCRA_keep()) || ctr == st->ctr);
memMove(ctr, st->ctr, 8);
}
err_t botpOCRARand(char* otp, const char* suite, const octet key[],
size_t key_len, const octet q[], size_t q_len, const octet ctr[8],
const octet p[], const octet s[], tm_time_t t)
{
botp_ocra_st* state;
if (!strIsValid(suite) || !memIsValid(key, key_len))
return ERR_BAD_INPUT;
state = (botp_ocra_st*)blobCreate(botpOCRA_keep());
if (state == 0)
return ERR_OUTOFMEMORY;
if (!botpOCRAStart(state, suite, key, key_len))
{
blobClose(state);
return ERR_BAD_FORMAT;
}
if (q_len < 4 || q_len > 2 * state->q_max)
{
blobClose(state);
return ERR_BAD_PARAMS;
}
if (!memIsValid(otp, state->digit + 1) ||
state->ctr_len && !memIsValid(ctr, state->ctr_len) ||
!memIsValid(q, q_len) ||
state->p_len && !memIsValid(p, state->p_len) ||
state->s_len && !memIsValid(s, state->s_len))
{
blobClose(state);
return ERR_BAD_INPUT;
}
if (state->ts && t == TIME_ERR)
{
blobClose(state);
return ERR_BAD_TIME;
}
botpOCRAStepS(state, ctr, p, s);
botpOCRAStepR(otp, q, q_len, t, state);
blobClose(state);
return ERR_OK;
}
err_t botpOCRAVerify(const char* otp, const char* suite, const octet key[],
size_t key_len, const octet q[], size_t q_len, const octet ctr[8],
const octet p[], const octet s[], tm_time_t t)
{
botp_ocra_st* state;
bool_t success;
if (!strIsValid(suite) || !memIsValid(key, key_len))
return ERR_BAD_INPUT;
state = (botp_ocra_st*)blobCreate(botpOCRA_keep());
if (state == 0)
return ERR_OUTOFMEMORY;
if (!botpOCRAStart(state, suite, key, key_len))
{
blobClose(state);
return ERR_BAD_FORMAT;
}
if (q_len < 4 || q_len > 2 * state->q_max)
{
blobClose(state);
return ERR_BAD_PARAMS;
}
if (state->digit != strLen(otp))
{
blobClose(state);
return ERR_BAD_PWD;
}
if (!memIsValid(otp, state->digit + 1) ||
state->ctr_len && !memIsValid(ctr, state->ctr_len) ||
!memIsValid(q, q_len) ||
state->p_len && !memIsValid(p, state->p_len) ||
state->s_len && !memIsValid(s, state->s_len))
{
blobClose(state);
return ERR_BAD_INPUT;
}
if (state->ts && t == TIME_ERR)
{
blobClose(state);
return ERR_BAD_TIME;
}
botpOCRAStepS(state, ctr, p, s);
success = botpOCRAStepV(otp, q, q_len, t, state);
blobClose(state);
return success ? ERR_OK : ERR_BAD_PWD;
}