openconnect-unknown/oath.c

1 /*
2  * OpenConnect (SSL + DTLS) VPN client
3  *
4  * Copyright © 2008-2015 Intel Corporation.
5  * Copyright © 2013 John Morrissey <jwm@horde.net>
6 
7  * Author: David Woodhouse <dwmw2@infradead.org>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * version 2.1, as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  */
18 
19 #include <config.h>
20 
21 #include <ctype.h>
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "openconnect-internal.h"
27 
28 static int b32_char(char in)
29 {
30 	if (in >= 'A' && in <= 'Z')
31 		return in - 'A';
32 	if (in >= 'a' && in <= 'z')
33 		return in - 'a';
34 	if (in >= '2' && in <= '7')
35 		return in - '2' + 26;
36 	if (in == '=')
37 		return -2;
38 	return -1;
39 }
40 
41 static int decode_b32_group(unsigned char *out, const char *in)
42 {
43 	uint32_t d = 0;
44 	int c, i, len;
45 
46 	for (i = 0; i < 8; i++) {
47 		c = b32_char(in[i]);
48 		if (c == -1)
49 			return -EINVAL;
50 		if (c == -2)
51 			break;
52 		d <<= 5;
53 		d |= c;
54 
55 		/* Write the top bits before they disappear off the top
56 		   of 'd' which is only a uint32_t */
57 		if (i == 1)
58 			out[0] = d >> 2;
59 	}
60 	len = i;
61 	if (i < 8) {
62 		d <<= 5 * (8 - i);
63 		while (++i < 8) {
64 			if (in[i] != '=')
65 				return -EINVAL;
66 		}
67 	}
68 
69 	store_be32(out + 1, d);
70 
71 	switch(len) {
72 	case 8:
73 		return 5;
74 	case 7:
75 		return 4;
76 	case 5:
77 		return 3;
78 	case 4:
79 		return 2;
80 	case 2:
81 		return 1;
82 	default:
83 		return -EINVAL;
84 	}
85 }
86 
87 static int decode_base32(struct openconnect_info *vpninfo, const char *b32, int len)
88 {
89 	unsigned char *output = NULL;
90 	int inpos, outpos;
91 	int outlen;
92 	int ret;
93 
94 	if (len % 8) {
95 	invalid:
96 		vpn_progress(vpninfo, PRG_ERR,
97 			     _("Invalid base32 token string\n"));
98 		free(output);
99 		return -EINVAL;
100 	}
101 	outlen = len / 8 * 5;
102 	output = malloc(outlen);
103 	if (!output) {
104 		vpn_progress(vpninfo, PRG_ERR,
105 			     _("Failed to allocate memory to decode OATH secret\n"));
106 		return -ENOMEM;
107 	}
108 	outpos = inpos = 0;
109 	while (inpos < len) {
110 		ret = decode_b32_group(output + outpos, b32 + inpos);
111 		if (ret < 0)
112 			goto invalid;
113 
114 		inpos += 8;
115 		if (ret != 5 && inpos != len)
116 			goto invalid;
117 		outpos += ret;
118 	}
119 	vpninfo->oath_secret = (void *)output;
120 	vpninfo->oath_secret_len = outpos;
121 	return 0;
122 }
123 
124 static char *parse_hex(const char *tok, int len)
125 {
126 	unsigned char *data, *p;
127 
128 	data = malloc((len + 1) / 2);
129 	if (!data)
130 		return NULL;
131 
132 	p = data;
133 
134 	if (len & 1) {
135 		char b[2] = { '0', tok[0] };
136 		if (!isxdigit((int)(unsigned char)tok[0])) {
137 			free(data);
138 			return NULL;
139 		}
140 		*(p++) = unhex(b);
141 		tok++;
142 		len--;
143 	}
144 
145 	while (len) {
146 		if (!isxdigit((int)(unsigned char)tok[0]) ||
147 		    !isxdigit((int)(unsigned char)tok[1])) {
148 			free(data);
149 			return NULL;
150 		}
151 		*(p++) = unhex(tok);
152 		tok += 2;
153 		len -= 2;
154 	}
155 
156 	return (char *)data;
157 }
158 
159 static int pskc_decode(struct openconnect_info *vpninfo, const char *token_str,
160 		       int toklen, int mode)
161 {
162 #ifdef HAVE_LIBPSKC
163 	pskc_t *container;
164 	pskc_key_t *key;
165 	const char *key_algo;
166 	const char *want_algo;
167 	size_t klen;
168 
169 	if (pskc_global_init())
170 		return -EIO;
171 
172 	if (pskc_init(&container))
173 		return -ENOMEM;
174 
175 	if (pskc_parse_from_memory(container, toklen, token_str))
176 		return -EINVAL;
177 
178 	key = pskc_get_keypackage(container, 0);
179 	if (!key) {
180 		pskc_done(container);
181 		return -EINVAL;
182 	}
183 	if (mode == OC_TOKEN_MODE_HOTP)
184 		want_algo = "urn:ietf:params:xml:ns:keyprov:pskc:hotp";
185 	else
186 		want_algo = "urn:ietf:params:xml:ns:keyprov:pskc:totp";
187 	key_algo = pskc_get_key_algorithm(key);
188 
189 	if (!key_algo || strcmp(key_algo, want_algo)) {
190 		pskc_done(container);
191 		return -EINVAL;
192 	}
193 
194 	vpninfo->oath_secret = (char *)pskc_get_key_data_secret(key, &klen);
195 	vpninfo->oath_secret_len = klen;
196 	if (!vpninfo->oath_secret) {
197 		pskc_done(container);
198 		return -EINVAL;
199 	}
200 	vpninfo->token_time = pskc_get_key_data_counter(key, NULL);
201 
202 	vpninfo->pskc = container;
203 	vpninfo->pskc_key = key;
204 
205 	return 0;
206 #else /* !HAVE_LIBPSKC */
207 	vpn_progress(vpninfo, PRG_ERR,
208 		     _("This version of OpenConnect was built without PSKC support\n"));
209 	return -EINVAL;
210 #endif /* HAVE_LIBPSKC */
211 }
212 
213 int set_totp_mode(struct openconnect_info *vpninfo, const char *token_str)
214 {
215 	int ret, toklen;
216 
217 	if (!token_str)
218 		return -EINVAL;
219 
220 	toklen = strlen(token_str);
221 	while (toklen && isspace((int)(unsigned char)token_str[toklen-1]))
222 		toklen--;
223 
224 	if (strncmp(token_str, "<?xml", 5) == 0) {
225 		vpninfo->hotp_secret_format = HOTP_SECRET_PSKC;
226 		ret = pskc_decode(vpninfo, token_str, toklen, OC_TOKEN_MODE_TOTP);
227 		if (ret)
228 			return -EINVAL;
229 		vpninfo->token_mode = OC_TOKEN_MODE_TOTP;
230 		return 0;
231 	}
232 	if (!strncasecmp(token_str, "sha1:", 5)) {
233 		token_str += 5;
234 		toklen -= 5;
235 		vpninfo->oath_hmac_alg = OATH_ALG_HMAC_SHA1;
236 	} else if (!strncasecmp(token_str, "sha256:", 7)) {
237 		token_str += 7;
238 		toklen -= 7;
239 		vpninfo->oath_hmac_alg = OATH_ALG_HMAC_SHA256;
240 	} else if (!strncasecmp(token_str, "sha512:", 7)) {
241 		token_str += 7;
242 		toklen -= 7;
243 		vpninfo->oath_hmac_alg = OATH_ALG_HMAC_SHA512;
244 	} else
245 		vpninfo->oath_hmac_alg = OATH_ALG_HMAC_SHA1;
246 
247 	if (strncasecmp(token_str, "base32:", strlen("base32:")) == 0) {
248 		ret = decode_base32(vpninfo, token_str + strlen("base32:"),
249 				    toklen - strlen("base32:"));
250 		if (ret)
251 			return ret;
252 	} else if (strncmp(token_str, "0x", 2) == 0) {
253 		vpninfo->oath_secret_len = (toklen - 2) / 2;
254 		vpninfo->oath_secret = parse_hex(token_str + 2, toklen - 2);
255 		if (!vpninfo->oath_secret)
256 			return -EINVAL;
257 	} else {
258 		vpninfo->oath_secret = strdup(token_str);
259 		vpninfo->oath_secret_len = toklen;
260 	}
261 
262 	vpninfo->token_mode = OC_TOKEN_MODE_TOTP;
263 	return 0;
264 }
265 
266 int set_hotp_mode(struct openconnect_info *vpninfo, const char *token_str)
267 {
268 	int ret, toklen;
269 	char *p;
270 
271 	if (!token_str)
272 		return -EINVAL;
273 
274 	toklen = strlen(token_str);
275 
276 	if (strncmp(token_str, "<?xml", 5) == 0) {
277 		vpninfo->hotp_secret_format = HOTP_SECRET_PSKC;
278 		ret = pskc_decode(vpninfo, token_str, toklen, OC_TOKEN_MODE_HOTP);
279 		if (ret)
280 			return -EINVAL;
281 		vpninfo->token_mode = OC_TOKEN_MODE_HOTP;
282 		return 0;
283 	}
284 
285 	if (!strncasecmp(token_str, "sha1:", 5)) {
286 		token_str += 5;
287 		toklen -= 5;
288 		vpninfo->oath_hmac_alg = OATH_ALG_HMAC_SHA1;
289 	} else if (!strncasecmp(token_str, "sha256:", 7)) {
290 		token_str += 7;
291 		toklen -= 7;
292 		vpninfo->oath_hmac_alg = OATH_ALG_HMAC_SHA256;
293 	} else if (!strncasecmp(token_str, "sha512:", 7)) {
294 		toklen -= 7;
295 		token_str += 7;
296 		vpninfo->oath_hmac_alg = OATH_ALG_HMAC_SHA512;
297 	} else
298 		vpninfo->oath_hmac_alg = OATH_ALG_HMAC_SHA1;
299 
300 	p = strrchr(token_str, ',');
301 	if (p) {
302 		long counter;
303 		toklen = p - token_str;
304 		p++;
305 		counter = strtol(p, &p, 0);
306 		if (counter < 0)
307 			return -EINVAL;
308 		while (*p) {
309 			if (isspace((int)(unsigned char)*p))
310 				p++;
311 			else
312 				return -EINVAL;
313 		}
314 		vpninfo->token_time = counter;
315 	} else {
316 		while (toklen &&
317 		       isspace((int)(unsigned char)token_str[toklen-1]))
318 			toklen--;
319 	}
320 
321 	if (strncasecmp(token_str, "base32:", strlen("base32:")) == 0) {
322 		vpninfo->hotp_secret_format = HOTP_SECRET_BASE32;
323 		ret = decode_base32(vpninfo, token_str + strlen("base32:"),
324 				    toklen - strlen("base32:"));
325 		if (ret)
326 			return ret;
327 	} else if (strncmp(token_str, "0x", 2) == 0) {
328 		vpninfo->hotp_secret_format = HOTP_SECRET_HEX;
329 		vpninfo->oath_secret_len = (toklen - 2) / 2;
330 		vpninfo->oath_secret = parse_hex(token_str + 2, toklen - 2);
331 		if (!vpninfo->oath_secret)
332 			return -EINVAL;
333 	} else {
334 		vpninfo->hotp_secret_format = HOTP_SECRET_RAW;
335 		vpninfo->oath_secret = strdup(token_str);
336 		vpninfo->oath_secret_len = toklen;
337 	}
338 
339 	vpninfo->token_mode = OC_TOKEN_MODE_HOTP;
340 	return 0;
341 }
342 
343 /* Return value:
344  *  < 0, if unable to generate a tokencode
345  *  = 0, on success
346  */
347 int can_gen_totp_code(struct openconnect_info *vpninfo,
348 		      struct oc_auth_form *form,
349 		      struct oc_form_opt *opt)
350 {
351 	if (vpninfo->token_tries == 0) {
352 		vpn_progress(vpninfo, PRG_DEBUG,
353 			     _("OK to generate INITIAL tokencode\n"));
354 		vpninfo->token_time = 0;
355 	} else if (vpninfo->token_tries == 1) {
356 		vpn_progress(vpninfo, PRG_DEBUG,
357 			     _("OK to generate NEXT tokencode\n"));
358 		vpninfo->token_time += 30;
359 	} else {
360 		/* limit the number of retries, to avoid account lockouts */
361 		vpn_progress(vpninfo, PRG_INFO,
362 			     _("Server is rejecting the soft token; switching to manual entry\n"));
363 		return -ENOENT;
364 	}
365 	return 0;
366 }
367 
368 /* Return value:
369  *  < 0, if unable to generate a tokencode
370  *  = 0, on success
371  */
372 int can_gen_hotp_code(struct openconnect_info *vpninfo,
373 		      struct oc_auth_form *form,
374 		      struct oc_form_opt *opt)
375 {
376 	if (vpninfo->token_tries == 0) {
377 		vpn_progress(vpninfo, PRG_DEBUG,
378 			     _("OK to generate INITIAL tokencode\n"));
379 	} else if (vpninfo->token_tries == 1) {
380 		vpn_progress(vpninfo, PRG_DEBUG,
381 			     _("OK to generate NEXT tokencode\n"));
382 	} else {
383 		/* limit the number of retries, to avoid account lockouts */
384 		vpn_progress(vpninfo, PRG_INFO,
385 			     _("Server is rejecting the soft token; switching to manual entry\n"));
386 		return -ENOENT;
387 	}
388 	return 0;
389 }
390 
391 static int gen_hotp(struct openconnect_info *vpninfo, uint64_t data, char *output)
392 {
393 	uint32_t data_be[2];
394 	int digest;
395 
396 	data_be[0] = htonl(data >> 32);
397 	data_be[1] = htonl(data);
398 
399 	digest = hotp_hmac(vpninfo, data_be);
400 	if (digest < 0)
401 		return digest;
402 
403 	digest %= 1000000;
404 	snprintf(output, 7, "%06d", digest);
405 
406 	return 0;
407 }
408 
409 int do_gen_totp_code(struct openconnect_info *vpninfo,
410 		     struct oc_auth_form *form,
411 		     struct oc_form_opt *opt)
412 {
413 	char tokencode[7];
414 	uint64_t challenge;
415 
416 	if (!vpninfo->token_time)
417 		vpninfo->token_time = time(NULL);
418 
419 	vpn_progress(vpninfo, PRG_INFO, _("Generating OATH TOTP token code\n"));
420 
421 	/* XXX: Support non-standard start time and step size */
422 	challenge = vpninfo->token_time / 30;
423 
424 	if (gen_hotp(vpninfo, challenge, tokencode))
425 		return -EIO;
426 
427 	vpninfo->token_tries++;
428 	opt->_value = strdup(tokencode);
429 	return opt->_value ? 0 : -ENOMEM;
430 }
431 
432 static void buf_append_base32(struct oc_text_buf *buf, void *data, int len)
433 {
434 	static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
435 	unsigned char *bytes = data;
436 	int i, j, b32_len = ((len + 4) / 5) * 8;
437 	uint32_t d;
438 	char b32[8];
439 
440 	if (buf_ensure_space(buf, b32_len + 1))
441 		return;
442 
443 	for (i = 0; i < (len - 4); i += 5) {
444 		/* Load low 4 input bytes into 'd' */
445 		d = load_be32(&bytes[i + 1]);
446 		/* Loop backwardd over output group, emitting low
447 		 * 5 bits of 'd' each time and shifting. */
448 		for (j = 7; j >= 0; j--) {
449 			b32[j] = alphabet[d & 31];
450 			d >>= 5;
451 			/* Mask in the last input byte when we can fit it */
452 			if (j == 5)
453 				d |= bytes[i] << 17;
454 		}
455 		buf_append_bytes(buf, b32, 8);
456 	}
457 	if (i < len) {
458 		d = 0;
459 		/* This is basically load_be32(bytes + i) but substituting
460 		 * zeroes instead of reading off the end. */
461 		for (j = 0; j < 4; j++) {
462 			d <<= 8;
463 			if (i + j < len)
464 				d |= bytes[i + j];
465 		}
466 		/* Now, work out how much '=' padding we need */
467 		memset(b32, '=', 8);
468 		b32_len = (((len - i) * 8) + 4) / 5;
469 		memset(b32 + b32_len, '=', 8 - b32_len);
470 		/* If we need 7 characters of data then put the seventh
471 		 * in manually because the LSB of 'd' is actually bit 3
472 		 * of the output character. */
473 		if (b32_len == 7) {
474 			b32[6] = alphabet[(d & 3) << 3];
475 			b32_len--;
476 		}
477 		/* Now shift bits into the right place and do the simple
478 		 * loop emitting characters from the low 5 bits of 'd'. */
479 		d >>= ((8 - b32_len) * 5) - 8;
480 		for (j = b32_len - 1; j >= 0; j--) {
481 			b32[j] = alphabet[d & 31];
482 			d >>= 5;
483 		}
484 		buf_append_bytes(buf, b32, 8);
485 	}
486 }
487 
488 static char *regen_hotp_secret(struct openconnect_info *vpninfo)
489 {
490 	char *new_secret = NULL;
491 	struct oc_text_buf *buf;
492 
493 	switch (vpninfo->hotp_secret_format) {
494 	case HOTP_SECRET_BASE32:
495 		buf = buf_alloc();
496 		buf_append(buf, "base32:");
497 		buf_append_base32(buf, vpninfo->oath_secret,
498 				  vpninfo->oath_secret_len);
499 		break;
500 
501 	case HOTP_SECRET_HEX:
502 		buf = buf_alloc();
503 		buf_append(buf, "0x");
504 		buf_append_hex(buf, vpninfo->oath_secret, vpninfo->oath_secret_len);
505 		break;
506 
507 	case HOTP_SECRET_RAW:
508 		buf = buf_alloc();
509 		buf_append_bytes(buf, vpninfo->oath_secret,
510 				 vpninfo->oath_secret_len);
511 		break;
512 
513 	case HOTP_SECRET_PSKC:
514 #ifdef HAVE_LIBPSKC
515 	{
516 		size_t len;
517 		if (!vpninfo->pskc_key || !vpninfo->pskc)
518 			return NULL;
519 		pskc_set_key_data_counter(vpninfo->pskc_key, vpninfo->token_time);
520 		pskc_build_xml(vpninfo->pskc, &new_secret, &len);
521 		/* FFS #1: libpskc craps all over itself on pskc_build_xml().
522 		   https://bugzilla.redhat.com/show_bug.cgi?id=1129491
523 		   Hopefully this will be fixed by 2.4.2 but make it
524 		   unconditional for now... */
525 		if (1 || !pskc_check_version("2.4.2")) {
526 			pskc_done(vpninfo->pskc);
527 			vpninfo->pskc = NULL;
528 			vpninfo->pskc_key = NULL;
529 			if (pskc_init(&vpninfo->pskc) ||
530 			    pskc_parse_from_memory(vpninfo->pskc, len, new_secret)) {
531 				pskc_done(vpninfo->pskc);
532 				vpninfo->pskc = NULL;
533 			} else {
534 				vpninfo->pskc_key = pskc_get_keypackage(vpninfo->pskc, 0);
535 				vpninfo->oath_secret = (char *)pskc_get_key_data_secret(vpninfo->pskc_key, NULL);
536 			}
537 		}
538 		/* FFS #2: No terminating NUL byte */
539 		realloc_inplace(new_secret, len + 1);
540 		if (new_secret)
541 			new_secret[len] = 0;
542 		return new_secret;
543 	}
544 #endif
545 	default:
546 		return NULL;
547 	}
548 
549 	buf_append(buf,",%ld", (long)vpninfo->token_time);
550 	if (!buf_error(buf)) {
551 		new_secret = buf->data;
552 		buf->data = NULL;
553 	}
554 	buf_free(buf);
555 	return new_secret;
556 }
557 
558 int do_gen_hotp_code(struct openconnect_info *vpninfo,
559 		     struct oc_auth_form *form,
560 		     struct oc_form_opt *opt)
561 {
562 	char tokencode[7];
563 	int ret;
564 
565 	vpn_progress(vpninfo, PRG_INFO, _("Generating OATH HOTP token code\n"));
566 
567 	if (vpninfo->lock_token) {
568 		/* This may call openconnect_set_token_mode() again to update
569 		 * the token if it's changed. */
570 		ret = vpninfo->lock_token(vpninfo->tok_cbdata);
571 		if (ret)
572 			return ret;
573 	}
574 	if (gen_hotp(vpninfo, vpninfo->token_time, tokencode))
575 		return -EIO;
576 
577 	vpninfo->token_time++;
578 	vpninfo->token_tries++;
579 	opt->_value = strdup(tokencode);
580 	if (vpninfo->unlock_token) {
581 		char *new_tok = regen_hotp_secret(vpninfo);
582 		vpninfo->unlock_token(vpninfo->tok_cbdata, new_tok);
583 		free(new_tok);
584 	}
585 	return opt->_value ? 0 : -ENOMEM;
586 }