aboutsummaryrefslogtreecommitdiff
path: root/src/authorization/anastasis_authorization_plugin_totp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/authorization/anastasis_authorization_plugin_totp.c')
-rw-r--r--src/authorization/anastasis_authorization_plugin_totp.c376
1 files changed, 376 insertions, 0 deletions
diff --git a/src/authorization/anastasis_authorization_plugin_totp.c b/src/authorization/anastasis_authorization_plugin_totp.c
new file mode 100644
index 0000000..6fcdd39
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin_totp.c
@@ -0,0 +1,376 @@
1/*
2 This totp is part of Anastasis
3 Copyright (C) 2021 Anastasis SARL
4
5 Anastasis is free software; you can redistribute it and/or modify it under the
6 terms of the GNU Lesser General Public License as published by the Free Software
7 Foundation; either version 3, or (at your option) any later version.
8
9 Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
12
13 You should have received a copy of the GNU Affero General Public License along with
14 Anastasis; see the totp COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
15*/
16/**
17 * @totp anastasis_authorization_plugin_totp.c
18 * @brief authorization plugin using totp
19 * @author Christian Grothoff
20 */
21#include "platform.h"
22#include "anastasis_authorization_plugin.h"
23#include <taler/taler_mhd_lib.h>
24#include <gnunet/gnunet_db_lib.h>
25#include "anastasis_database_lib.h"
26#include <gcrypt.h>
27
28
29/**
30 * How many retries do we allow per code?
31 */
32#define INITIAL_RETRY_COUNTER 3
33
34/**
35 * How long is a TOTP code valid?
36 */
37#define TOTP_VALIDITY_PERIOD GNUNET_TIME_relative_multiply ( \
38 GNUNET_TIME_UNIT_SECONDS, 30)
39
40/**
41 * Range of time we allow (plus-minus).
42 */
43#define TIME_INTERVAL_RANGE 2
44
45/**
46 * How long is the shared secret in bytes?
47 */
48#define SECRET_LEN 32
49
50
51/**
52 * Saves the state of a authorization process
53 */
54struct ANASTASIS_AUTHORIZATION_State
55{
56 /**
57 * UUID of the challenge which is authorised
58 */
59 struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
60
61 /**
62 * Our context.
63 */
64 const struct ANASTASIS_AuthorizationContext *ac;
65
66 /**
67 * Was the challenge satisfied?
68 */
69 bool ok;
70
71};
72
73
74/**
75 * Validate @a data is a well-formed input into the challenge method,
76 * i.e. @a data is a well-formed phone number for sending an SMS, or
77 * a well-formed e-mail address for sending an e-mail. Not expected to
78 * check that the phone number or e-mail account actually exists.
79 *
80 * To be possibly used before issuing a 402 payment required to the client.
81 *
82 * @param cls closure with a `const struct ANASTASIS_AuthorizationContext *`
83 * @param connection HTTP client request (for queuing response)
84 * @param truth_mime mime type of @e data
85 * @param data input to validate (i.e. is it a valid phone number, etc.)
86 * @param data_length number of bytes in @a data
87 * @return #GNUNET_OK if @a data is valid,
88 * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
89 * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
90 */
91static enum GNUNET_GenericReturnValue
92totp_validate (void *cls,
93 struct MHD_Connection *connection,
94 const char *truth_mime,
95 const char *data,
96 size_t data_length)
97{
98 (void) cls;
99 (void) truth_mime;
100 (void) connection;
101 if (NULL == data)
102 {
103 GNUNET_break_op (0);
104 if (MHD_NO ==
105 TALER_MHD_reply_with_error (connection,
106 MHD_HTTP_EXPECTATION_FAILED,
107 TALER_EC_ANASTASIS_TOTP_KEY_MISSING,
108 NULL))
109 return GNUNET_SYSERR;
110 return GNUNET_NO;
111 }
112 if (SECRET_LEN != data_length)
113 {
114 GNUNET_break_op (0);
115 if (MHD_NO ==
116 TALER_MHD_reply_with_error (connection,
117 MHD_HTTP_EXPECTATION_FAILED,
118 TALER_EC_ANASTASIS_TOTP_KEY_INVALID,
119 NULL))
120 return GNUNET_SYSERR;
121 return GNUNET_NO;
122 }
123 return GNUNET_OK;
124}
125
126
127/**
128 * Compute TOTP code at current time with offset
129 * @a time_off for the @a key.
130 *
131 * @param time_off offset to apply when computing the code
132 * @param key input key material
133 * @param key_size number of bytes in @a key
134 * @return TOTP code at this time
135 */
136static uint64_t
137compute_totp (int time_off,
138 const void *key,
139 size_t key_size)
140{
141 struct GNUNET_TIME_Absolute now;
142 time_t t;
143 uint64_t ctr;
144 uint8_t hmac[16]; /* SHA1: 16 bytes */
145
146 now = GNUNET_TIME_absolute_get ();
147 while (time_off < 0)
148 {
149 now = GNUNET_TIME_absolute_subtract (now,
150 TOTP_VALIDITY_PERIOD);
151 time_off++;
152 }
153 while (time_off > 0)
154 {
155 now = GNUNET_TIME_absolute_add (now,
156 TOTP_VALIDITY_PERIOD);
157 time_off--;
158 }
159 t = now.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us;
160 ctr = GNUNET_htonll (t / 30LLU);
161
162 {
163 gcry_md_hd_t md;
164 const unsigned char *mc;
165
166 GNUNET_assert (GPG_ERR_NO_ERROR ==
167 gcry_md_open (&md,
168 GCRY_MD_SHA1,
169 GCRY_MD_FLAG_HMAC));
170 gcry_md_setkey (md,
171 key,
172 key_size);
173 gcry_md_write (md,
174 &ctr,
175 sizeof (ctr));
176 mc = gcry_md_read (md,
177 GCRY_MD_SHA1);
178 GNUNET_assert (NULL != mc);
179 memcpy (hmac,
180 mc,
181 sizeof (hmac));
182 gcry_md_close (md);
183 }
184
185 {
186 uint32_t code = 0;
187
188 for (int count = 0; count < 4; count++)
189 code += hmac[(hmac[sizeof (hmac) - 1] & 0x0f) + 3 - count] << 8 * count;
190 code &= 0x7fffffff;
191
192#if VAR_DIGITS
193 if (digits == 6)
194 code = code % 1000000;
195 else if (digits == 7)
196 code = code % 10000000;
197 else if (digits == 8)
198 code = code % 100000000;
199#else
200 code = code % 1000000;
201#endif
202 return code;
203 }
204}
205
206
207/**
208 * Begin issuing authentication challenge to user based on @a data.
209 *
210 * @param cls closure
211 * @param trigger function to call when we made progress
212 * @param trigger_cls closure for @a trigger
213 * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
214 * interaction with the user
215 * @param code set to secret code that the user provided to satisfy the challenge in
216 * the main anastasis protocol
217 * @param data input to validate (i.e. the shared secret)
218 * @param data_length number of bytes in @a data
219 * @return state to track progress on the authorization operation, NULL on failure
220 */
221static struct ANASTASIS_AUTHORIZATION_State *
222totp_start (void *cls,
223 GNUNET_SCHEDULER_TaskCallback trigger,
224 void *trigger_cls,
225 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
226 uint64_t code,
227 const void *data,
228 size_t data_length)
229{
230 const struct ANASTASIS_AuthorizationContext *ac = cls;
231 struct ANASTASIS_AUTHORIZATION_State *as;
232 uint64_t want;
233
234 as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
235 as->ac = ac;
236 as->truth_uuid = *truth_uuid;
237 for (int i = -TIME_INTERVAL_RANGE;
238 i < TIME_INTERVAL_RANGE;
239 i++)
240 {
241 want = compute_totp (i,
242 data,
243 data_length);
244 if (code == want)
245 as->ok = true;
246 }
247 return as;
248}
249
250
251/**
252 * Begin issuing authentication challenge to user based on @a data.
253 *
254 * @param as authorization state
255 * @param timeout how long do we have to produce a reply
256 * @param connection HTTP client request (for queuing response, such as redirection to video portal)
257 * @return state of the request
258 */
259static enum ANASTASIS_AUTHORIZATION_Result
260totp_process (struct ANASTASIS_AUTHORIZATION_State *as,
261 struct GNUNET_TIME_Absolute timeout,
262 struct MHD_Connection *connection)
263{
264 MHD_RESULT mres;
265 const char *mime;
266 const char *lang;
267
268 if (as->ok)
269 return ANASTASIS_AUTHORIZATION_RES_FINISHED;
270 mime = MHD_lookup_connection_value (connection,
271 MHD_HEADER_KIND,
272 MHD_HTTP_HEADER_ACCEPT);
273 if (NULL == mime)
274 mime = "text/plain";
275 lang = MHD_lookup_connection_value (connection,
276 MHD_HEADER_KIND,
277 MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
278 if (NULL == lang)
279 lang = "en";
280
281 /* Build HTTP response */
282 {
283 struct MHD_Response *resp;
284 struct GNUNET_TIME_Absolute now;
285
286 now = GNUNET_TIME_absolute_get ();
287 if (TALER_MHD_xmime_matches (mime,
288 "application/json"))
289 {
290 resp = TALER_MHD_MAKE_JSON_PACK (
291 GNUNET_JSON_pack_time_abs ("server_time",
292 now));
293 }
294 else
295 {
296 size_t response_size;
297 char *response;
298
299 // FIXME: i18n of the message based on 'lang' ...
300 response_size
301 = GNUNET_asprintf (&response,
302 "Server time: %s",
303 GNUNET_STRINGS_absolute_time_to_string (now));
304 resp = MHD_create_response_from_buffer (response_size,
305 response,
306 MHD_RESPMEM_MUST_COPY);
307 TALER_MHD_add_global_headers (resp);
308 GNUNET_break (MHD_YES ==
309 MHD_add_response_header (resp,
310 MHD_HTTP_HEADER_CONTENT_TYPE,
311 "text/plain"));
312 }
313 mres = MHD_queue_response (connection,
314 MHD_HTTP_FORBIDDEN,
315 resp);
316 MHD_destroy_response (resp);
317 }
318 if (MHD_YES != mres)
319 return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED;
320 return ANASTASIS_AUTHORIZATION_RES_SUCCESS;
321}
322
323
324/**
325 * Free internal state associated with @a as.
326 *
327 * @param as state to clean up
328 */
329static void
330totp_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
331{
332 GNUNET_free (as);
333}
334
335
336/**
337 * Initialize Totp based authorization plugin
338 *
339 * @param cls a configuration instance
340 * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
341 */
342void *
343libanastasis_plugin_authorization_totp_init (void *cls)
344{
345 const struct ANASTASIS_AuthorizationContext *ac = cls;
346 struct ANASTASIS_AuthorizationPlugin *plugin;
347
348 plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
349 plugin->cls = (void *) ac;
350 plugin->user_provided_code = true;
351 plugin->retry_counter = INITIAL_RETRY_COUNTER;
352 plugin->code_validity_period = TOTP_VALIDITY_PERIOD;
353 plugin->code_rotation_period = plugin->code_validity_period;
354 plugin->code_retransmission_frequency = plugin->code_validity_period;
355 plugin->validate = &totp_validate;
356 plugin->start = &totp_start;
357 plugin->process = &totp_process;
358 plugin->cleanup = &totp_cleanup;
359 return plugin;
360}
361
362
363/**
364 * Unload authorization plugin
365 *
366 * @param cls a `struct ANASTASIS_AuthorizationPlugin`
367 * @return NULL (always)
368 */
369void *
370libanastasis_plugin_authorization_totp_done (void *cls)
371{
372 struct ANASTASIS_AuthorizationPlugin *plugin = cls;
373
374 GNUNET_free (plugin);
375 return NULL;
376}