The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
totp.c
Go to the documentation of this file.
1 /*
2  * This program is is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or (at
5  * your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: 76f4c30578c6df7937169cc70bed018df6780cf5 $
19  * @file totp.c
20  * @brief Common function for TOTP validation.
21  *
22  * @copyright 2023 The FreeRADIUS server project
23  */
24 #include <freeradius-devel/util/sha1.h>
25 #include <freeradius-devel/util/debug.h>
26 
27 #include "totp.h"
28 
29 #ifdef TESTING
30 #include <freeradius-devel/util/talloc.h>
31 #include <freeradius-devel/util/dbuff.h>
32 #include <freeradius-devel/util/sbuff.h>
33 #include <freeradius-devel/util/base32.h>
34 
35 #undef RDEBUG_ENABLED3
36 #define RDEBUG_ENABLED3 (!request)
37 #undef RDEBUG3
38 #define RDEBUG3(fmt, ...) totp_log(fmt, ## __VA_ARGS__)
39 
40 DIAG_OFF(format-nonliteral)
41 
42 static void totp_log(char const *fmt, ...)
43 {
44  va_list ap;
45 
46  va_start(ap, fmt);
47  vfprintf(stdout, fmt, ap);
48  va_end(ap);
49 
50  printf("\n");
51 }
52 #endif
53 
54 /** Implement RFC 6238 TOTP algorithm (HMAC-SHA1).
55  *
56  * Appendix B has test vectors. Note that the test vectors are
57  * for 8-character challenges, and not for 6 character
58  * challenges!
59  *
60  * @param[in] cfg Instance of fr_totp_t
61  * @param[in] request The current request
62  * @param[in] now The current time
63  * @param[in] key Key to encrypt.
64  * @param[in] keylen Length of key field.
65  * @param[in] totp TOTP password entered by the user.
66  * @return
67  * - 0 On Success
68  * - -1 On Failure
69  * - -2 On incorrect arguments
70  */
71 int fr_totp_cmp(fr_totp_t const *cfg, request_t *request, time_t now, uint8_t const *key, size_t keylen, char const *totp)
72 {
73  time_t diff, then;
74  uint32_t steps;
75  unsigned int i;
76  uint8_t offset;
77  uint32_t challenge;
78  uint64_t padded;
79  uint8_t data[8];
81  char buffer[9];
82 
83  fr_assert(cfg->otp_length == 6 || cfg->otp_length == 8);
84 
85 
86  if (cfg->otp_length != 6 && cfg->otp_length != 8) {
87  fr_strerror_const("The 'opt_length' has incorrect length. Expected 6 or 8.");
88  return -2;
89  }
90 
91  if (keylen < 1) {
92  fr_strerror_const("Invalid 'keylen' parameter value.");
93  return -2;
94  }
95 
96  if (!*totp) {
97  fr_strerror_const("Invalid 'totp' parameter value.");
98  return -2;
99  }
100 
101  /*
102  * First try to authenticate against the current OTP, then step
103  * back and forwards in increments of `lookback_interval`, up to `lookback_steps` times,
104  * to authenticate properly in cases of long transit delay, as
105  * described in RFC 6238, section 5.2.
106  */
107  steps = cfg->lookback_steps > cfg->lookforward_steps ? cfg->lookback_steps : cfg->lookforward_steps;
108  for (i = 0, diff = 0; i <= steps; i++, diff += cfg->lookback_interval) {
109  if (i > cfg->lookback_steps) goto forwards;
110  then = now - diff;
111  repeat:
112  padded = ((uint64_t) then) / cfg->time_step;
113  data[0] = padded >> 56;
114  data[1] = padded >> 48;
115  data[2] = padded >> 40;
116  data[3] = padded >> 32;
117  data[4] = padded >> 24;
118  data[5] = padded >> 16;
119  data[6] = padded >> 8;
120  data[7] = padded & 0xff;
121 
122  /*
123  * Encrypt the network order time with the key.
124  */
125  fr_hmac_sha1(digest, data, 8, key, keylen);
126 
127  /*
128  * Take the least significant 4 bits.
129  */
130  offset = digest[SHA1_DIGEST_LENGTH - 1] & 0x0f;
131 
132  /*
133  * Grab the 32bits at "offset", and drop the high bit.
134  */
135  challenge = (digest[offset] & 0x7f) << 24;
136  challenge |= digest[offset + 1] << 16;
137  challenge |= digest[offset + 2] << 8;
138  challenge |= digest[offset + 3];
139 
140  /*
141  * The token is the last 6 digits in the number (or 8 for testing)..
142  */
143  snprintf(buffer, sizeof(buffer), ((cfg->otp_length == 6) ? "%06u" : "%08u"),
144  challenge % ((cfg->otp_length == 6) ? 1000000 : 100000000));
145 
146  if (RDEBUG_ENABLED3) {
147  char buf_now[32], buf_then[32];
148  fr_sbuff_t snow = FR_SBUFF_IN(buf_now, sizeof(buf_now));
149  fr_sbuff_t sthen = FR_SBUFF_IN(buf_then, sizeof(buf_then));
150 
151  fr_time_strftime_local(&snow, fr_time_wrap(now), "%a %b %d %H:%M:%S %Y");
152  fr_time_strftime_local(&sthen, fr_time_wrap(then), "%a %b %d %H:%M:%S %Y");
153 
154  RDEBUG3("Now: %zu (%s), Then: %zu (%s)", (size_t) now, fr_sbuff_start(&snow), (size_t) then, fr_sbuff_start(&sthen));
155  RDEBUG3("Expected %s", buffer);
156  RDEBUG3("Received %s", totp);
157  }
158 
159  if (fr_digest_cmp((uint8_t const *) buffer, (uint8_t const *) totp, cfg->otp_length) == 0) return 0;
160 
161  /*
162  * We've tested backwards, now do the equivalent time slot forwards
163  */
164  if ((then < now) && (i <= cfg->lookforward_steps)) {
165  forwards:
166  then = now + offset;
167  goto repeat;
168  }
169  }
170 
171  return -1;
172 }
173 
174 #ifdef TESTING
175 int main(int argc, char **argv)
176 {
177  size_t len;
178  uint8_t *p;
179  uint8_t key[80];
180  fr_totp_t totp = {
181  .time_step = 30,
182  .otp_length = 8,
183  .lookback_steps = 1,
184  .lookback_interval = 1,
185  };
186 
187  if (argc < 2) {
188  fprintf(stderr, "totp: Expected command 'decode' or 'totp'\n");
189  return 1;
190  }
191 
192  if (strcmp(argv[1], "decode") == 0) {
193  if (argc < 3) {
194  fprintf(stderr, "totp: Expected arguments as - decode <base32-data> \n");
195  return 1;
196  }
197 
198  len = fr_base32_decode(&FR_DBUFF_TMP(key, sizeof(key)), &FR_SBUFF_IN(argv[2], strlen(argv[2])), true, true);
199  printf("Decoded %ld %s\n", len, key);
200 
201  for (p = key; p < (key + len); p++) {
202  printf("%02x ", *p);
203  }
204  printf("\n");
205 
206  return 0;
207  }
208 
209  /*
210  * TOTP <time> <key> <8-character-expected-token>
211  */
212  if (strcmp(argv[1], "totp") == 0) {
213  uint64_t now;
214 
215  if (argc < 5) {
216  fprintf(stderr, "totp: Expected arguments as - totp <time> <key> <totp>\n");
217  return 1;
218  }
219 
220  (void) sscanf(argv[2], "%llu", &now);
221 
222  if (fr_totp_cmp(&totp, NULL, (time_t) now, (uint8_t const *) argv[3], strlen(argv[3]), argv[4]) == 0) {
223  return 0;
224  }
225  printf("Fail\n");
226  return 1;
227  }
228 
229  fprintf(stderr, "Unknown command %s\n", argv[1]);
230  return 1;
231 }
232 #endif
static int const char char buffer[256]
Definition: acutest.h:574
va_end(args)
static int const char * fmt
Definition: acutest.h:573
va_start(args, fmt)
#define fr_base32_decode(_out, _in, _expect_padding, _no_trailing)
Definition: base32.h:69
#define DIAG_OFF(_x)
Definition: build.h:418
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition: dbuff.h:509
int main(int argc, char **argv)
Definition: dhcpclient.c:521
int fr_hmac_sha1(uint8_t digest[static SHA1_DIGEST_LENGTH], uint8_t const *in, size_t inlen, uint8_t const *key, size_t key_len)
Calculate HMAC using internal SHA1 implementation.
Definition: hmac_sha1.c:124
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
Definition: log.h:335
#define RDEBUG3(fmt,...)
Definition: log.h:343
unsigned int uint32_t
Definition: merged_model.c:33
unsigned char uint8_t
Definition: merged_model.c:30
int fr_digest_cmp(uint8_t const *a, uint8_t const *b, size_t length)
Do a comparison of two authentication digests by comparing the FULL data.
Definition: misc.c:463
#define fr_sbuff_start(_sbuff_or_marker)
#define FR_SBUFF_IN(_start, _len_or_end)
#define SHA1_DIGEST_LENGTH
Definition: sha1.h:29
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:689
fr_assert(0)
size_t fr_time_strftime_local(fr_sbuff_t *out, fr_time_t time, char const *fmt)
Copy a time string (local timezone) to an sbuff.
Definition: time.c:536
#define fr_time_wrap(_time)
Definition: time.h:145
int fr_totp_cmp(fr_totp_t const *cfg, request_t *request, time_t now, uint8_t const *key, size_t keylen, char const *totp)
Implement RFC 6238 TOTP algorithm (HMAC-SHA1).
Definition: totp.c:71
uint32_t otp_length
forced to 6 or 8
Definition: totp.h:35
uint32_t lookforward_steps
number of steps to look forwards
Definition: totp.h:38
uint32_t lookback_interval
interval in seconds between steps
Definition: totp.h:37
uint32_t lookback_steps
number of steps to look back
Definition: totp.h:36
uint32_t time_step
seconds
Definition: totp.h:34
Definition: totp.h:33
#define fr_strerror_const(_msg)
Definition: strerror.h:223
static fr_slen_t data
Definition: value.h:1259
int format(printf, 5, 0))