1*7ca9ec5dSgsoares /* $OpenBSD: login_yubikey.c,v 1.16 2016/09/03 11:01:44 gsoares Exp $ */
25e4064a0Sdhill 
35e4064a0Sdhill /*
45e4064a0Sdhill  * Copyright (c) 2010 Daniel Hartmeier <daniel@benzedrine.cx>
55e4064a0Sdhill  * All rights reserved.
65e4064a0Sdhill  *
75e4064a0Sdhill  * Redistribution and use in source and binary forms, with or without
85e4064a0Sdhill  * modification, are permitted provided that the following conditions
95e4064a0Sdhill  * are met:
105e4064a0Sdhill  *
115e4064a0Sdhill  *    - Redistributions of source code must retain the above copyright
125e4064a0Sdhill  *      notice, this list of conditions and the following disclaimer.
135e4064a0Sdhill  *    - Redistributions in binary form must reproduce the above
145e4064a0Sdhill  *      copyright notice, this list of conditions and the following
155e4064a0Sdhill  *      disclaimer in the documentation and/or other materials provided
165e4064a0Sdhill  *      with the distribution.
175e4064a0Sdhill  *
185e4064a0Sdhill  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
195e4064a0Sdhill  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
205e4064a0Sdhill  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
215e4064a0Sdhill  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
225e4064a0Sdhill  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
235e4064a0Sdhill  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
245e4064a0Sdhill  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
255e4064a0Sdhill  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
265e4064a0Sdhill  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
275e4064a0Sdhill  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
285e4064a0Sdhill  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
295e4064a0Sdhill  * POSSIBILITY OF SUCH DAMAGE.
305e4064a0Sdhill  *
315e4064a0Sdhill  */
325e4064a0Sdhill 
335b588232Shalex #include <sys/stat.h>
345e4064a0Sdhill #include <sys/time.h>
355e4064a0Sdhill #include <sys/resource.h>
36f4b29b64Sbmercer #include <sys/unistd.h>
375e4064a0Sdhill #include <ctype.h>
385e4064a0Sdhill #include <login_cap.h>
395e4064a0Sdhill #include <pwd.h>
40*7ca9ec5dSgsoares #include <readpassphrase.h>
415e4064a0Sdhill #include <stdarg.h>
425e4064a0Sdhill #include <stdio.h>
435e4064a0Sdhill #include <stdlib.h>
445e4064a0Sdhill #include <string.h>
455e4064a0Sdhill #include <syslog.h>
465e4064a0Sdhill #include <unistd.h>
47b9fc9a72Sderaadt #include <limits.h>
483ef0233cSmcbride #include <errno.h>
495e4064a0Sdhill 
505e4064a0Sdhill #include "yubikey.h"
515e4064a0Sdhill 
525e4064a0Sdhill #define	MODE_LOGIN	0
535e4064a0Sdhill #define	MODE_CHALLENGE	1
545e4064a0Sdhill #define	MODE_RESPONSE	2
555e4064a0Sdhill 
565e4064a0Sdhill #define	AUTH_OK		0
575e4064a0Sdhill #define	AUTH_FAILED	-1
585e4064a0Sdhill 
595e4064a0Sdhill static const char *path = "/var/db/yubikey";
605e4064a0Sdhill 
615e4064a0Sdhill static int clean_string(const char *);
625e4064a0Sdhill static int yubikey_login(const char *, const char *);
635e4064a0Sdhill 
644c0831f1Sdhill int
main(int argc,char * argv[])654c0831f1Sdhill main(int argc, char *argv[])
665e4064a0Sdhill {
67d6ec0ebcSderaadt 	int ch, ret, mode = MODE_LOGIN, count;
685e4064a0Sdhill 	FILE *f = NULL;
695e4064a0Sdhill 	char *username, *password = NULL;
70*7ca9ec5dSgsoares 	char pbuf[1024];
715e4064a0Sdhill 	char response[1024];
725e4064a0Sdhill 
735e4064a0Sdhill 	setpriority(PRIO_PROCESS, 0, 0);
74f4b29b64Sbmercer 
75f4b29b64Sbmercer 	if (pledge("stdio tty wpath rpath cpath", NULL) == -1) {
76f4b29b64Sbmercer 		syslog(LOG_AUTH|LOG_ERR, "pledge: %m");
77f4b29b64Sbmercer 		exit(EXIT_FAILURE);
78f4b29b64Sbmercer 	}
79f4b29b64Sbmercer 
805e4064a0Sdhill 	openlog(NULL, LOG_ODELAY, LOG_AUTH);
815e4064a0Sdhill 
825e4064a0Sdhill 	while ((ch = getopt(argc, argv, "dv:s:")) != -1) {
835e4064a0Sdhill 		switch (ch) {
845e4064a0Sdhill 		case 'd':
855e4064a0Sdhill 			f = stdout;
865e4064a0Sdhill 			break;
875e4064a0Sdhill 		case 'v':
885e4064a0Sdhill 			break;
895e4064a0Sdhill 		case 's':
905e4064a0Sdhill 			if (!strcmp(optarg, "login"))
915e4064a0Sdhill 				mode = MODE_LOGIN;
925e4064a0Sdhill 			else if (!strcmp(optarg, "response"))
935e4064a0Sdhill 				mode = MODE_RESPONSE;
945e4064a0Sdhill 			else if (!strcmp(optarg, "challenge"))
955e4064a0Sdhill 				mode = MODE_CHALLENGE;
965e4064a0Sdhill 			else {
975e4064a0Sdhill 				syslog(LOG_ERR, "%s: invalid service", optarg);
985e4064a0Sdhill 				exit(EXIT_FAILURE);
995e4064a0Sdhill 			}
1005e4064a0Sdhill 			break;
1015e4064a0Sdhill 		default:
1025e4064a0Sdhill 			syslog(LOG_ERR, "usage error1");
1035e4064a0Sdhill 			exit(EXIT_FAILURE);
1045e4064a0Sdhill 		}
1055e4064a0Sdhill 	}
1065e4064a0Sdhill 	argc -= optind;
1075e4064a0Sdhill 	argv += optind;
1085e4064a0Sdhill 	if (argc != 2 && argc != 1) {
1095e4064a0Sdhill 		syslog(LOG_ERR, "usage error2");
1105e4064a0Sdhill 		exit(EXIT_FAILURE);
1115e4064a0Sdhill 	}
1125e4064a0Sdhill 	username = argv[0];
1135e4064a0Sdhill 	/* passed by sshd(8) for non-existing users */
1145e4064a0Sdhill 	if (!strcmp(username, "NOUSER"))
1155e4064a0Sdhill 		exit(EXIT_FAILURE);
1165e4064a0Sdhill 	if (!clean_string(username)) {
1175e4064a0Sdhill 		syslog(LOG_ERR, "clean_string username");
1185e4064a0Sdhill 		exit(EXIT_FAILURE);
1195e4064a0Sdhill 	}
1205e4064a0Sdhill 
1215e4064a0Sdhill 	if (f == NULL && (f = fdopen(3, "r+")) == NULL) {
1225e4064a0Sdhill 		syslog(LOG_ERR, "user %s: fdopen: %m", username);
1235e4064a0Sdhill 		exit(EXIT_FAILURE);
1245e4064a0Sdhill 	}
1255e4064a0Sdhill 
1265e4064a0Sdhill 	switch (mode) {
1275e4064a0Sdhill 	case MODE_LOGIN:
128*7ca9ec5dSgsoares 		if ((password = readpassphrase("Password:", pbuf, sizeof(pbuf),
129*7ca9ec5dSgsoares 			    RPP_ECHO_OFF)) == NULL) {
130*7ca9ec5dSgsoares 			syslog(LOG_ERR, "user %s: readpassphrase: %m",
1315e4064a0Sdhill 			    username);
1325e4064a0Sdhill 			exit(EXIT_FAILURE);
1335e4064a0Sdhill 		}
1345e4064a0Sdhill 		break;
1355e4064a0Sdhill 	case MODE_CHALLENGE:
1365e4064a0Sdhill 		/* see login.conf(5) section CHALLENGES */
1375e4064a0Sdhill 		fprintf(f, "%s\n", BI_SILENT);
1385e4064a0Sdhill 		exit(EXIT_SUCCESS);
1395e4064a0Sdhill 		break;
140d6ec0ebcSderaadt 	case MODE_RESPONSE:
1415e4064a0Sdhill 		/* see login.conf(5) section RESPONSES */
1425e4064a0Sdhill 		/* this happens e.g. when called from sshd(8) */
1435e4064a0Sdhill 		mode = 0;
1445e4064a0Sdhill 		count = -1;
1455e4064a0Sdhill 		while (++count < sizeof(response) &&
14623ae9128Stedu 		    read(3, &response[count], 1) == 1) {
1475e4064a0Sdhill 			if (response[count] == '\0' && ++mode == 2)
1485e4064a0Sdhill 				break;
149d6ec0ebcSderaadt 			if (response[count] == '\0' && mode == 1)
1505e4064a0Sdhill 				password = response + count + 1;
1515e4064a0Sdhill 		}
1525e4064a0Sdhill 		if (mode < 2) {
1535e4064a0Sdhill 			syslog(LOG_ERR, "user %s: protocol error "
1545e4064a0Sdhill 			    "on back channel", username);
1555e4064a0Sdhill 			exit(EXIT_FAILURE);
1565e4064a0Sdhill 		}
1575e4064a0Sdhill 		break;
1585e4064a0Sdhill 	}
1595e4064a0Sdhill 
1605e4064a0Sdhill 	ret = yubikey_login(username, password);
16148d9b361Smillert 	explicit_bzero(password, strlen(password));
1625e4064a0Sdhill 	if (ret == AUTH_OK) {
1635e4064a0Sdhill 		syslog(LOG_INFO, "user %s: authorize", username);
1645e4064a0Sdhill 		fprintf(f, "%s\n", BI_AUTH);
1655e4064a0Sdhill 	} else {
1665e4064a0Sdhill 		syslog(LOG_INFO, "user %s: reject", username);
1675e4064a0Sdhill 		fprintf(f, "%s\n", BI_REJECT);
1685e4064a0Sdhill 	}
1695e4064a0Sdhill 	closelog();
1705e4064a0Sdhill 	return (EXIT_SUCCESS);
1715e4064a0Sdhill }
1725e4064a0Sdhill 
1735e4064a0Sdhill static int
clean_string(const char * s)1745e4064a0Sdhill clean_string(const char *s)
1755e4064a0Sdhill {
1765e4064a0Sdhill 	while (*s) {
1774207a9b6Sderaadt 		if (!isalnum((unsigned char)*s) && *s != '-' && *s != '_')
1785e4064a0Sdhill 			return (0);
1795e4064a0Sdhill 		++s;
1805e4064a0Sdhill 	}
1815e4064a0Sdhill 	return (1);
1825e4064a0Sdhill }
1835e4064a0Sdhill 
1845e4064a0Sdhill static int
yubikey_login(const char * username,const char * password)1855e4064a0Sdhill yubikey_login(const char *username, const char *password)
1865e4064a0Sdhill {
187b9fc9a72Sderaadt 	char fn[PATH_MAX];
1885e4064a0Sdhill 	char hexkey[33], key[YUBIKEY_KEY_SIZE];
1895e4064a0Sdhill 	char hexuid[13], uid[YUBIKEY_UID_SIZE];
1905e4064a0Sdhill 	FILE *f;
1915e4064a0Sdhill 	yubikey_token_st tok;
1925e4064a0Sdhill 	u_int32_t last_ctr = 0, ctr;
1933ef0233cSmcbride 	int r, i = 0, mapok = 0, crcok = 0;
1945e4064a0Sdhill 
1955e4064a0Sdhill 	snprintf(fn, sizeof(fn), "%s/%s.uid", path, username);
1965e4064a0Sdhill 	if ((f = fopen(fn, "r")) == NULL) {
1975e4064a0Sdhill 		syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn);
1985e4064a0Sdhill 		return (AUTH_FAILED);
1995e4064a0Sdhill 	}
2005e4064a0Sdhill 	if (fscanf(f, "%12s", hexuid) != 1) {
2015e4064a0Sdhill 		syslog(LOG_ERR, "user %s: fscanf: %s: %m", username, fn);
2025e4064a0Sdhill 		fclose(f);
2035e4064a0Sdhill 		return (AUTH_FAILED);
2045e4064a0Sdhill 	}
2055e4064a0Sdhill 	fclose(f);
2065e4064a0Sdhill 
2075e4064a0Sdhill 	snprintf(fn, sizeof(fn), "%s/%s.key", path, username);
2085e4064a0Sdhill 	if ((f = fopen(fn, "r")) == NULL) {
2095e4064a0Sdhill 		syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn);
2105e4064a0Sdhill 		return (AUTH_FAILED);
2115e4064a0Sdhill 	}
2125e4064a0Sdhill 	if (fscanf(f, "%32s", hexkey) != 1) {
2135e4064a0Sdhill 		syslog(LOG_ERR, "user %s: fscanf: %s: %m", username, fn);
2145e4064a0Sdhill 		fclose(f);
2155e4064a0Sdhill 		return (AUTH_FAILED);
2165e4064a0Sdhill 	}
2175e4064a0Sdhill 	fclose(f);
2185e4064a0Sdhill 	if (strlen(hexkey) != 32) {
2195e4064a0Sdhill 		syslog(LOG_ERR, "user %s: key len != 32", username);
2205e4064a0Sdhill 		return (AUTH_FAILED);
2215e4064a0Sdhill 	}
2225e4064a0Sdhill 
2235e4064a0Sdhill 	snprintf(fn, sizeof(fn), "%s/%s.ctr", path, username);
2245e4064a0Sdhill 	if ((f = fopen(fn, "r")) != NULL) {
2255e4064a0Sdhill 		if (fscanf(f, "%u", &last_ctr) != 1)
2265e4064a0Sdhill 			last_ctr = 0;
2275e4064a0Sdhill 		fclose(f);
2285e4064a0Sdhill 	}
2295e4064a0Sdhill 
2305e4064a0Sdhill 	yubikey_hex_decode(uid, hexuid, YUBIKEY_UID_SIZE);
2315e4064a0Sdhill 	yubikey_hex_decode(key, hexkey, YUBIKEY_KEY_SIZE);
2323ef0233cSmcbride 
2339e81ffdbSbenno 	explicit_bzero(hexkey, sizeof(hexkey));
2349e81ffdbSbenno 
2353ef0233cSmcbride 	/*
2363ef0233cSmcbride 	 * Cycle through the key mapping table.
2373ef0233cSmcbride          * XXX brute force, unoptimized; a lookup table for valid mappings may
2383ef0233cSmcbride 	 * be appropriate.
2393ef0233cSmcbride 	 */
2403ef0233cSmcbride 	while (1) {
2413ef0233cSmcbride 		r = yubikey_parse((uint8_t *)password, (uint8_t *)key, &tok, i++);
2423ef0233cSmcbride 		switch (r) {
2433ef0233cSmcbride 		case EMSGSIZE:
244784473b7Sbenno 			syslog(LOG_INFO, "user %s failed: password too short.",
245784473b7Sbenno 			    username);
2469e81ffdbSbenno 			explicit_bzero(key, sizeof(key));
2475e4064a0Sdhill 			return (AUTH_FAILED);
2483ef0233cSmcbride 		case EINVAL:	/* keyboard mapping invalid */
2493ef0233cSmcbride 			continue;
2503ef0233cSmcbride 		case 0:		/* found a valid keyboard mapping */
2513ef0233cSmcbride 			mapok++;
2523ef0233cSmcbride 			if (!yubikey_crc_ok_p((uint8_t *)&tok))
2533ef0233cSmcbride 				continue;	/* try another one */
2543ef0233cSmcbride 			crcok++;
2553ef0233cSmcbride 			syslog(LOG_DEBUG, "user %s: crc %04x ok",
2563ef0233cSmcbride 			    username, tok.crc);
2575e4064a0Sdhill 
2585e4064a0Sdhill 			if (memcmp(tok.uid, uid, YUBIKEY_UID_SIZE)) {
2595e4064a0Sdhill 				char h[13];
2605e4064a0Sdhill 
2613ef0233cSmcbride 				yubikey_hex_encode(h, (const char *)tok.uid,
2623ef0233cSmcbride 				    YUBIKEY_UID_SIZE);
2633ef0233cSmcbride 				syslog(LOG_DEBUG, "user %s: uid %s != %s",
2643ef0233cSmcbride 				    username, h, hexuid);
2653ef0233cSmcbride 				continue;	/* try another one */
2663ef0233cSmcbride 			}
2673ef0233cSmcbride 			break; /* uid matches */
2683ef0233cSmcbride 		case -1:
269784473b7Sbenno 			syslog(LOG_INFO, "user %s: could not decode password "
2703ef0233cSmcbride 			    "with any keymap (%d crc ok)",
271784473b7Sbenno 			    username, crcok);
2729e81ffdbSbenno 			explicit_bzero(key, sizeof(key));
2733ef0233cSmcbride 			return (AUTH_FAILED);
2743ef0233cSmcbride 		default:
275784473b7Sbenno 			syslog(LOG_DEBUG, "user %s failed: %s",
276784473b7Sbenno 			    username, strerror(r));
2779e81ffdbSbenno 			explicit_bzero(key, sizeof(key));
2785e4064a0Sdhill 			return (AUTH_FAILED);
2795e4064a0Sdhill 		}
2803ef0233cSmcbride 		break; /* only reached through the bottom of case 0 */
2813ef0233cSmcbride 	}
2823ef0233cSmcbride 
2839e81ffdbSbenno 	explicit_bzero(key, sizeof(key));
2849e81ffdbSbenno 
2853ef0233cSmcbride 	syslog(LOG_INFO, "user %s uid %s: %d matching keymaps (%d checked), "
2863ef0233cSmcbride 	    "%d crc ok", username, hexuid, mapok, i, crcok);
2875e4064a0Sdhill 
2885e4064a0Sdhill 	ctr = ((u_int32_t)yubikey_counter(tok.ctr) << 8) | tok.use;
2895e4064a0Sdhill 	if (ctr <= last_ctr) {
2905e4064a0Sdhill 		syslog(LOG_INFO, "user %s: counter %u.%u <= %u.%u "
2915e4064a0Sdhill 		    "(REPLAY ATTACK!)", username, ctr / 256, ctr % 256,
2925e4064a0Sdhill 		    last_ctr / 256, last_ctr % 256);
2935e4064a0Sdhill 		return (AUTH_FAILED);
2945e4064a0Sdhill 	}
2955e4064a0Sdhill 	syslog(LOG_INFO, "user %s: counter %u.%u > %u.%u",
2965e4064a0Sdhill 	    username, ctr / 256, ctr % 256, last_ctr / 256, last_ctr % 256);
2975b588232Shalex 	umask(S_IRWXO);
2985e4064a0Sdhill 	if ((f = fopen(fn, "w")) == NULL) {
2995e4064a0Sdhill 		syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn);
3005e4064a0Sdhill 		return (AUTH_FAILED);
3015e4064a0Sdhill 	}
3025e4064a0Sdhill 	fprintf(f, "%u", ctr);
3035e4064a0Sdhill 	fclose(f);
3045e4064a0Sdhill 
3055e4064a0Sdhill 	return (AUTH_OK);
3065e4064a0Sdhill }
307