The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
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: 681eebdea02c7757b79d4d0d2a3f7f9bdccb412e $
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
40DIAG_OFF(format-nonliteral)
41
42static 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 */
71int 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 + diff;
167 goto repeat;
168 }
169 }
170
171 return -1;
172}
173
174#ifdef TESTING
175int 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:576
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:457
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:514
int main(int argc, char **argv)
Definition dhcpclient.c:524
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
unsigned char uint8_t
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:472
#define fr_assert(_expr)
Definition rad_assert.h:38
#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
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:540
#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
#define fr_strerror_const(_msg)
Definition strerror.h:223
static fr_slen_t data
Definition value.h:1265