/*
This file is part of TALER
Copyright (C) 2023 Taler Systems SA
TALER 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, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see
*/
/**
* @file util/crypto_confirmation.c
* @brief confirmation computation
* @author Christian Grothoff
* @author Priscilla Huang
*/
#include "platform.h"
#include "taler_util.h"
#include "taler_mhd_lib.h"
#include
#include
/**
* How long is a TOTP code valid?
*/
#define TOTP_VALIDITY_PERIOD GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_SECONDS, 30)
/**
* Range of time we allow (plus-minus).
*/
#define TIME_INTERVAL_RANGE 2
/**
* Compute TOTP code at current time with offset
* @a time_off for the @a key.
*
* @param ts current time
* @param time_off offset to apply when computing the code
* @param key pos_key in binary
* @param key_size number of bytes in @a key
*/
static uint64_t
compute_totp (struct GNUNET_TIME_Timestamp ts,
int time_off,
const void *key,
size_t key_size)
{
struct GNUNET_TIME_Absolute now;
time_t t;
uint64_t ctr;
uint8_t hmac[20]; /* SHA1: 20 bytes */
now = ts.abs_time;
while (time_off < 0)
{
now = GNUNET_TIME_absolute_subtract (now,
TOTP_VALIDITY_PERIOD);
time_off++;
}
while (time_off > 0)
{
now = GNUNET_TIME_absolute_add (now,
TOTP_VALIDITY_PERIOD);
time_off--;
}
t = now.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us;
ctr = GNUNET_htonll (t / 30LLU);
{
gcry_md_hd_t md;
const unsigned char *mc;
GNUNET_assert (GPG_ERR_NO_ERROR ==
gcry_md_open (&md,
GCRY_MD_SHA1,
GCRY_MD_FLAG_HMAC));
GNUNET_assert (GPG_ERR_NO_ERROR ==
gcry_md_setkey (md,
key,
key_size));
gcry_md_write (md,
&ctr,
sizeof (ctr));
mc = gcry_md_read (md,
GCRY_MD_SHA1);
GNUNET_assert (NULL != mc);
GNUNET_memcpy (hmac,
mc,
sizeof (hmac));
gcry_md_close (md);
}
{
uint32_t code = 0;
int offset;
offset = hmac[sizeof (hmac) - 1] & 0x0f;
for (int count = 0; count < 4; count++)
code |= ((uint32_t) hmac[offset + 3 - count]) << (8 * count);
code &= 0x7fffffff;
/* always use 8 digits (maximum) */
code = code % 100000000;
return code;
}
}
int
TALER_rfc3548_base32decode (const char *val,
size_t val_size,
void *key,
size_t key_len)
{
/**
* 32 characters for decoding, using RFC 3548.
*/
static const char *decTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
unsigned char *udata = key;
unsigned int wpos = 0;
unsigned int rpos = 0;
unsigned int bits = 0;
unsigned int vbit = 0;
while ((rpos < val_size) || (vbit >= 8))
{
if ((rpos < val_size) && (vbit < 8))
{
char c = val[rpos++];
if (c == '=')
{
/* padding character */
if (rpos == val_size)
break; /* Ok, 1x '=' padding is allowed */
if ( ('=' == val[rpos]) &&
(rpos + 1 == val_size) )
break; /* Ok, 2x '=' padding is allowed */
return -1; /* invalid padding */
}
const char *p = strchr (decTable__, toupper (c));
if (! p)
{
/* invalid character */
return -1;
}
bits = (bits << 5) | (p - decTable__);
vbit += 5;
}
if (vbit >= 8)
{
udata[wpos++] = (bits >> (vbit - 8)) & 0xFF;
vbit -= 8;
}
}
return wpos;
}
/**
* @brief Builds POS confirmation to verify payment.
*
* @param h_key opaque key for the totp operation
* @param h_key_len size of h_key in bytes
* @param ts current time
* @return Token on success, NULL of failure
*/
static char *
executive_totp (void *h_key,
size_t h_key_len,
struct GNUNET_TIME_Timestamp ts)
{
uint64_t code; /* totp code */
char *ret;
ret = NULL;
for (int i = -TIME_INTERVAL_RANGE; i<= TIME_INTERVAL_RANGE; i++)
{
code = compute_totp (ts,
i,
h_key,
h_key_len);
if (NULL == ret)
{
GNUNET_asprintf (&ret,
"%08llu",
(unsigned long long) code);
}
else
{
char *tmp;
GNUNET_asprintf (&tmp,
"%s\n%08llu",
ret,
(unsigned long long) code);
GNUNET_free (ret);
ret = tmp;
}
}
return ret;
}
char *
TALER_build_pos_confirmation (const char *pos_key,
enum TALER_MerchantConfirmationAlgorithm pos_alg,
const struct TALER_Amount *total,
struct GNUNET_TIME_Timestamp ts)
{
size_t pos_key_length = strlen (pos_key);
void *key; /* pos_key in binary */
size_t key_len; /* length of the key */
char *ret;
int dret;
if (TALER_MCA_NONE == pos_alg)
return NULL;
key_len = pos_key_length * 5 / 8;
key = GNUNET_malloc (key_len);
dret = TALER_rfc3548_base32decode (pos_key,
pos_key_length,
key,
key_len);
if (-1 == dret)
{
GNUNET_free (key);
GNUNET_break_op (0);
return NULL;
}
GNUNET_assert (dret <= key_len);
key_len = (size_t) dret;
switch (pos_alg)
{
case TALER_MCA_NONE:
GNUNET_break (0);
GNUNET_free (key);
return NULL;
case TALER_MCA_WITHOUT_PRICE: /* and 30s */
/* Return all T-OTP codes in range separated by new lines, e.g.
"12345678
24522552
25262425
42543525
25253552"
*/
ret = executive_totp (key,
key_len,
ts);
GNUNET_free (key);
return ret;
case TALER_MCA_WITH_PRICE:
{
struct GNUNET_HashCode hkey;
struct TALER_AmountNBO ntotal;
if ( (NULL == total) ||
(GNUNET_YES !=
TALER_amount_is_valid (total) ) )
{
GNUNET_break_op (0);
return NULL;
}
TALER_amount_hton (&ntotal,
total);
GNUNET_assert (GNUNET_YES ==
GNUNET_CRYPTO_kdf (&hkey,
sizeof (hkey),
&ntotal,
sizeof (ntotal),
key,
key_len,
NULL,
0));
GNUNET_free (key);
ret = executive_totp (&hkey,
sizeof(hkey),
ts);
GNUNET_free (key);
return ret;
}
}
GNUNET_free (key);
GNUNET_break (0);
return NULL;
}