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 |
|
|
} |