xref: /openbsd-src/libexec/login_yubikey/login_yubikey.c (revision 7ca9ec5d37dddfe4f0c06e265da9cc92a3603686)
1 /* $OpenBSD: login_yubikey.c,v 1.16 2016/09/03 11:01:44 gsoares Exp $ */
2 
3 /*
4  * Copyright (c) 2010 Daniel Hartmeier <daniel@benzedrine.cx>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  *    - Redistributions of source code must retain the above copyright
12  *      notice, this list of conditions and the following disclaimer.
13  *    - Redistributions in binary form must reproduce the above
14  *      copyright notice, this list of conditions and the following
15  *      disclaimer in the documentation and/or other materials provided
16  *      with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  *
31  */
32 
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include <sys/resource.h>
36 #include <sys/unistd.h>
37 #include <ctype.h>
38 #include <login_cap.h>
39 #include <pwd.h>
40 #include <readpassphrase.h>
41 #include <stdarg.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <syslog.h>
46 #include <unistd.h>
47 #include <limits.h>
48 #include <errno.h>
49 
50 #include "yubikey.h"
51 
52 #define	MODE_LOGIN	0
53 #define	MODE_CHALLENGE	1
54 #define	MODE_RESPONSE	2
55 
56 #define	AUTH_OK		0
57 #define	AUTH_FAILED	-1
58 
59 static const char *path = "/var/db/yubikey";
60 
61 static int clean_string(const char *);
62 static int yubikey_login(const char *, const char *);
63 
64 int
main(int argc,char * argv[])65 main(int argc, char *argv[])
66 {
67 	int ch, ret, mode = MODE_LOGIN, count;
68 	FILE *f = NULL;
69 	char *username, *password = NULL;
70 	char pbuf[1024];
71 	char response[1024];
72 
73 	setpriority(PRIO_PROCESS, 0, 0);
74 
75 	if (pledge("stdio tty wpath rpath cpath", NULL) == -1) {
76 		syslog(LOG_AUTH|LOG_ERR, "pledge: %m");
77 		exit(EXIT_FAILURE);
78 	}
79 
80 	openlog(NULL, LOG_ODELAY, LOG_AUTH);
81 
82 	while ((ch = getopt(argc, argv, "dv:s:")) != -1) {
83 		switch (ch) {
84 		case 'd':
85 			f = stdout;
86 			break;
87 		case 'v':
88 			break;
89 		case 's':
90 			if (!strcmp(optarg, "login"))
91 				mode = MODE_LOGIN;
92 			else if (!strcmp(optarg, "response"))
93 				mode = MODE_RESPONSE;
94 			else if (!strcmp(optarg, "challenge"))
95 				mode = MODE_CHALLENGE;
96 			else {
97 				syslog(LOG_ERR, "%s: invalid service", optarg);
98 				exit(EXIT_FAILURE);
99 			}
100 			break;
101 		default:
102 			syslog(LOG_ERR, "usage error1");
103 			exit(EXIT_FAILURE);
104 		}
105 	}
106 	argc -= optind;
107 	argv += optind;
108 	if (argc != 2 && argc != 1) {
109 		syslog(LOG_ERR, "usage error2");
110 		exit(EXIT_FAILURE);
111 	}
112 	username = argv[0];
113 	/* passed by sshd(8) for non-existing users */
114 	if (!strcmp(username, "NOUSER"))
115 		exit(EXIT_FAILURE);
116 	if (!clean_string(username)) {
117 		syslog(LOG_ERR, "clean_string username");
118 		exit(EXIT_FAILURE);
119 	}
120 
121 	if (f == NULL && (f = fdopen(3, "r+")) == NULL) {
122 		syslog(LOG_ERR, "user %s: fdopen: %m", username);
123 		exit(EXIT_FAILURE);
124 	}
125 
126 	switch (mode) {
127 	case MODE_LOGIN:
128 		if ((password = readpassphrase("Password:", pbuf, sizeof(pbuf),
129 			    RPP_ECHO_OFF)) == NULL) {
130 			syslog(LOG_ERR, "user %s: readpassphrase: %m",
131 			    username);
132 			exit(EXIT_FAILURE);
133 		}
134 		break;
135 	case MODE_CHALLENGE:
136 		/* see login.conf(5) section CHALLENGES */
137 		fprintf(f, "%s\n", BI_SILENT);
138 		exit(EXIT_SUCCESS);
139 		break;
140 	case MODE_RESPONSE:
141 		/* see login.conf(5) section RESPONSES */
142 		/* this happens e.g. when called from sshd(8) */
143 		mode = 0;
144 		count = -1;
145 		while (++count < sizeof(response) &&
146 		    read(3, &response[count], 1) == 1) {
147 			if (response[count] == '\0' && ++mode == 2)
148 				break;
149 			if (response[count] == '\0' && mode == 1)
150 				password = response + count + 1;
151 		}
152 		if (mode < 2) {
153 			syslog(LOG_ERR, "user %s: protocol error "
154 			    "on back channel", username);
155 			exit(EXIT_FAILURE);
156 		}
157 		break;
158 	}
159 
160 	ret = yubikey_login(username, password);
161 	explicit_bzero(password, strlen(password));
162 	if (ret == AUTH_OK) {
163 		syslog(LOG_INFO, "user %s: authorize", username);
164 		fprintf(f, "%s\n", BI_AUTH);
165 	} else {
166 		syslog(LOG_INFO, "user %s: reject", username);
167 		fprintf(f, "%s\n", BI_REJECT);
168 	}
169 	closelog();
170 	return (EXIT_SUCCESS);
171 }
172 
173 static int
clean_string(const char * s)174 clean_string(const char *s)
175 {
176 	while (*s) {
177 		if (!isalnum((unsigned char)*s) && *s != '-' && *s != '_')
178 			return (0);
179 		++s;
180 	}
181 	return (1);
182 }
183 
184 static int
yubikey_login(const char * username,const char * password)185 yubikey_login(const char *username, const char *password)
186 {
187 	char fn[PATH_MAX];
188 	char hexkey[33], key[YUBIKEY_KEY_SIZE];
189 	char hexuid[13], uid[YUBIKEY_UID_SIZE];
190 	FILE *f;
191 	yubikey_token_st tok;
192 	u_int32_t last_ctr = 0, ctr;
193 	int r, i = 0, mapok = 0, crcok = 0;
194 
195 	snprintf(fn, sizeof(fn), "%s/%s.uid", path, username);
196 	if ((f = fopen(fn, "r")) == NULL) {
197 		syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn);
198 		return (AUTH_FAILED);
199 	}
200 	if (fscanf(f, "%12s", hexuid) != 1) {
201 		syslog(LOG_ERR, "user %s: fscanf: %s: %m", username, fn);
202 		fclose(f);
203 		return (AUTH_FAILED);
204 	}
205 	fclose(f);
206 
207 	snprintf(fn, sizeof(fn), "%s/%s.key", path, username);
208 	if ((f = fopen(fn, "r")) == NULL) {
209 		syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn);
210 		return (AUTH_FAILED);
211 	}
212 	if (fscanf(f, "%32s", hexkey) != 1) {
213 		syslog(LOG_ERR, "user %s: fscanf: %s: %m", username, fn);
214 		fclose(f);
215 		return (AUTH_FAILED);
216 	}
217 	fclose(f);
218 	if (strlen(hexkey) != 32) {
219 		syslog(LOG_ERR, "user %s: key len != 32", username);
220 		return (AUTH_FAILED);
221 	}
222 
223 	snprintf(fn, sizeof(fn), "%s/%s.ctr", path, username);
224 	if ((f = fopen(fn, "r")) != NULL) {
225 		if (fscanf(f, "%u", &last_ctr) != 1)
226 			last_ctr = 0;
227 		fclose(f);
228 	}
229 
230 	yubikey_hex_decode(uid, hexuid, YUBIKEY_UID_SIZE);
231 	yubikey_hex_decode(key, hexkey, YUBIKEY_KEY_SIZE);
232 
233 	explicit_bzero(hexkey, sizeof(hexkey));
234 
235 	/*
236 	 * Cycle through the key mapping table.
237          * XXX brute force, unoptimized; a lookup table for valid mappings may
238 	 * be appropriate.
239 	 */
240 	while (1) {
241 		r = yubikey_parse((uint8_t *)password, (uint8_t *)key, &tok, i++);
242 		switch (r) {
243 		case EMSGSIZE:
244 			syslog(LOG_INFO, "user %s failed: password too short.",
245 			    username);
246 			explicit_bzero(key, sizeof(key));
247 			return (AUTH_FAILED);
248 		case EINVAL:	/* keyboard mapping invalid */
249 			continue;
250 		case 0:		/* found a valid keyboard mapping */
251 			mapok++;
252 			if (!yubikey_crc_ok_p((uint8_t *)&tok))
253 				continue;	/* try another one */
254 			crcok++;
255 			syslog(LOG_DEBUG, "user %s: crc %04x ok",
256 			    username, tok.crc);
257 
258 			if (memcmp(tok.uid, uid, YUBIKEY_UID_SIZE)) {
259 				char h[13];
260 
261 				yubikey_hex_encode(h, (const char *)tok.uid,
262 				    YUBIKEY_UID_SIZE);
263 				syslog(LOG_DEBUG, "user %s: uid %s != %s",
264 				    username, h, hexuid);
265 				continue;	/* try another one */
266 			}
267 			break; /* uid matches */
268 		case -1:
269 			syslog(LOG_INFO, "user %s: could not decode password "
270 			    "with any keymap (%d crc ok)",
271 			    username, crcok);
272 			explicit_bzero(key, sizeof(key));
273 			return (AUTH_FAILED);
274 		default:
275 			syslog(LOG_DEBUG, "user %s failed: %s",
276 			    username, strerror(r));
277 			explicit_bzero(key, sizeof(key));
278 			return (AUTH_FAILED);
279 		}
280 		break; /* only reached through the bottom of case 0 */
281 	}
282 
283 	explicit_bzero(key, sizeof(key));
284 
285 	syslog(LOG_INFO, "user %s uid %s: %d matching keymaps (%d checked), "
286 	    "%d crc ok", username, hexuid, mapok, i, crcok);
287 
288 	ctr = ((u_int32_t)yubikey_counter(tok.ctr) << 8) | tok.use;
289 	if (ctr <= last_ctr) {
290 		syslog(LOG_INFO, "user %s: counter %u.%u <= %u.%u "
291 		    "(REPLAY ATTACK!)", username, ctr / 256, ctr % 256,
292 		    last_ctr / 256, last_ctr % 256);
293 		return (AUTH_FAILED);
294 	}
295 	syslog(LOG_INFO, "user %s: counter %u.%u > %u.%u",
296 	    username, ctr / 256, ctr % 256, last_ctr / 256, last_ctr % 256);
297 	umask(S_IRWXO);
298 	if ((f = fopen(fn, "w")) == NULL) {
299 		syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn);
300 		return (AUTH_FAILED);
301 	}
302 	fprintf(f, "%u", ctr);
303 	fclose(f);
304 
305 	return (AUTH_OK);
306 }
307