GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: libexec/login_yubikey/login_yubikey.c Lines: 0 133 0.0 %
Date: 2017-11-07 Branches: 0 79 0.0 %

Line Branch Exec Source
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
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
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
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
}