/* 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; 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; }