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