pamu2fcfg.c revision 1.1.1.2 1 /*
2 * Copyright (C) 2014-2021 Yubico AB - See COPYING
3 */
4
5 #define BUFSIZE 1024
6 #define PAM_PREFIX "pam://"
7 #define TIMEOUT 15
8 #define FREQUENCY 1
9
10 #include <fido.h>
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <getopt.h>
16 #include <unistd.h>
17 #include <sys/types.h>
18 #include <pwd.h>
19
20 #include "b64.h"
21 #include "cmdline.h"
22 #include "util.h"
23
24 #include "openbsd-compat.h"
25
26 static fido_cred_t *prepare_cred(const struct gengetopt_args_info *const args) {
27 fido_cred_t *cred = NULL;
28 fido_opt_t resident_key;
29 char *appid = NULL;
30 char *user = NULL;
31 struct passwd *passwd;
32 unsigned char userid[32];
33 unsigned char cdh[32];
34 char origin[BUFSIZE];
35 int type;
36 int ok = -1;
37 size_t n;
38 int r;
39
40 if ((cred = fido_cred_new()) == NULL) {
41 fprintf(stderr, "fido_cred_new failed\n");
42 goto err;
43 }
44
45 type = COSE_ES256; /* default */
46 if (args->type_given) {
47 if (!cose_type(args->type_arg, &type)) {
48 fprintf(stderr, "Unknown COSE type '%s'.\n", args->type_arg);
49 goto err;
50 }
51 }
52
53 if ((r = fido_cred_set_type(cred, type)) != FIDO_OK) {
54 fprintf(stderr, "error: fido_cred_set_type (%d): %s\n", r, fido_strerr(r));
55 goto err;
56 }
57
58 if (!random_bytes(cdh, sizeof(cdh))) {
59 fprintf(stderr, "random_bytes failed\n");
60 goto err;
61 }
62
63 if ((r = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh))) != FIDO_OK) {
64 fprintf(stderr, "error: fido_cred_set_clientdata_hash (%d): %s\n", r,
65 fido_strerr(r));
66 goto err;
67 }
68
69 if (args->origin_given) {
70 if (strlcpy(origin, args->origin_arg, sizeof(origin)) >= sizeof(origin)) {
71 fprintf(stderr, "error: strlcpy failed\n");
72 goto err;
73 }
74 } else {
75 if ((n = strlcpy(origin, PAM_PREFIX, sizeof(origin))) >= sizeof(origin)) {
76 fprintf(stderr, "error: strlcpy failed\n");
77 goto err;
78 }
79 if (gethostname(origin + n, sizeof(origin) - n) == -1) {
80 perror("gethostname");
81 goto err;
82 }
83 }
84
85 if (args->appid_given) {
86 appid = args->appid_arg;
87 } else {
88 appid = origin;
89 }
90
91 if (args->verbose_given) {
92 fprintf(stderr, "Setting origin to %s\n", origin);
93 fprintf(stderr, "Setting appid to %s\n", appid);
94 }
95
96 if ((r = fido_cred_set_rp(cred, origin, appid)) != FIDO_OK) {
97 fprintf(stderr, "error: fido_cred_set_rp (%d) %s\n", r, fido_strerr(r));
98 goto err;
99 }
100
101 if (args->username_given) {
102 user = args->username_arg;
103 } else {
104 if ((passwd = getpwuid(getuid())) == NULL) {
105 perror("getpwuid");
106 goto err;
107 }
108 user = passwd->pw_name;
109 }
110
111 if (!random_bytes(userid, sizeof(userid))) {
112 fprintf(stderr, "random_bytes failed\n");
113 goto err;
114 }
115
116 if (args->verbose_given) {
117 fprintf(stderr, "Setting user to %s\n", user);
118 fprintf(stderr, "Setting user id to ");
119 for (size_t i = 0; i < sizeof(userid); i++)
120 fprintf(stderr, "%02x", userid[i]);
121 fprintf(stderr, "\n");
122 }
123
124 if ((r = fido_cred_set_user(cred, userid, sizeof(userid), user, user,
125 NULL)) != FIDO_OK) {
126 fprintf(stderr, "error: fido_cred_set_user (%d) %s\n", r, fido_strerr(r));
127 goto err;
128 }
129
130 if (args->resident_given) {
131 resident_key = FIDO_OPT_TRUE;
132 } else {
133 resident_key = FIDO_OPT_OMIT;
134 }
135
136 if ((r = fido_cred_set_rk(cred, resident_key)) != FIDO_OK) {
137 fprintf(stderr, "error: fido_cred_set_rk (%d) %s\n", r, fido_strerr(r));
138 goto err;
139 }
140
141 if ((r = fido_cred_set_uv(cred, FIDO_OPT_OMIT)) != FIDO_OK) {
142 fprintf(stderr, "error: fido_cred_set_uv (%d) %s\n", r, fido_strerr(r));
143 goto err;
144 }
145
146 ok = 0;
147
148 err:
149 if (ok != 0) {
150 fido_cred_free(&cred);
151 }
152
153 return cred;
154 }
155
156 static int make_cred(const char *path, fido_dev_t *dev, fido_cred_t *cred) {
157 char prompt[BUFSIZE];
158 char pin[BUFSIZE];
159 int n;
160 int r;
161
162 if (path == NULL || dev == NULL || cred == NULL) {
163 fprintf(stderr, "%s: args\n", __func__);
164 return -1;
165 }
166
167 r = fido_dev_make_cred(dev, cred, NULL);
168 if (r == FIDO_ERR_PIN_REQUIRED) {
169 n = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ", path);
170 if (n < 0 || (size_t) n >= sizeof(prompt)) {
171 fprintf(stderr, "error: snprintf prompt");
172 return -1;
173 }
174 if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF)) {
175 fprintf(stderr, "error: failed to read pin");
176 explicit_bzero(pin, sizeof(pin));
177 return -1;
178 }
179 r = fido_dev_make_cred(dev, cred, pin);
180 }
181 explicit_bzero(pin, sizeof(pin));
182
183 if (r != FIDO_OK) {
184 fprintf(stderr, "error: fido_dev_make_cred (%d) %s\n", r, fido_strerr(r));
185 return -1;
186 }
187
188 return 0;
189 }
190
191 static int verify_cred(const fido_cred_t *const cred) {
192 int r;
193
194 if (cred == NULL) {
195 fprintf(stderr, "%s: args\n", __func__);
196 return -1;
197 }
198
199 if (fido_cred_x5c_ptr(cred) == NULL) {
200 if ((r = fido_cred_verify_self(cred)) != FIDO_OK) {
201 fprintf(stderr, "error: fido_cred_verify_self (%d) %s\n", r,
202 fido_strerr(r));
203 return -1;
204 }
205 } else {
206 if ((r = fido_cred_verify(cred)) != FIDO_OK) {
207 fprintf(stderr, "error: fido_cred_verify (%d) %s\n", r, fido_strerr(r));
208 return -1;
209 }
210 }
211
212 return 0;
213 }
214
215 static int print_authfile_line(const struct gengetopt_args_info *const args,
216 const fido_cred_t *const cred) {
217 const unsigned char *kh = NULL;
218 const unsigned char *pk = NULL;
219 const char *user = NULL;
220 char *b64_kh = NULL;
221 char *b64_pk = NULL;
222 size_t kh_len;
223 size_t pk_len;
224 int ok = -1;
225
226 if ((kh = fido_cred_id_ptr(cred)) == NULL) {
227 fprintf(stderr, "error: fido_cred_id_ptr returned NULL\n");
228 goto err;
229 }
230
231 if ((kh_len = fido_cred_id_len(cred)) == 0) {
232 fprintf(stderr, "error: fido_cred_id_len returned 0\n");
233 goto err;
234 }
235
236 if ((pk = fido_cred_pubkey_ptr(cred)) == NULL) {
237 fprintf(stderr, "error: fido_cred_pubkey_ptr returned NULL\n");
238 goto err;
239 }
240
241 if ((pk_len = fido_cred_pubkey_len(cred)) == 0) {
242 fprintf(stderr, "error: fido_cred_pubkey_len returned 0\n");
243 goto err;
244 }
245
246 if (!b64_encode(kh, kh_len, &b64_kh)) {
247 fprintf(stderr, "error: failed to encode key handle\n");
248 goto err;
249 }
250
251 if (!b64_encode(pk, pk_len, &b64_pk)) {
252 fprintf(stderr, "error: failed to encode public key\n");
253 goto err;
254 }
255
256 if (!args->nouser_given) {
257 if ((user = fido_cred_user_name(cred)) == NULL) {
258 fprintf(stderr, "error: fido_cred_user_name returned NULL\n");
259 goto err;
260 }
261 printf("%s", user);
262 }
263
264 printf(":%s,%s,%s,%s%s%s", args->resident_given ? "*" : b64_kh, b64_pk,
265 cose_string(fido_cred_type(cred)),
266 !args->no_user_presence_given ? "+presence" : "",
267 args->user_verification_given ? "+verification" : "",
268 args->pin_verification_given ? "+pin" : "");
269
270 ok = 0;
271
272 err:
273 free(b64_kh);
274 free(b64_pk);
275
276 return ok;
277 }
278
279 int main(int argc, char *argv[]) {
280 int exit_code = EXIT_FAILURE;
281 struct gengetopt_args_info args_info;
282 fido_cred_t *cred = NULL;
283 fido_dev_info_t *devlist = NULL;
284 fido_dev_t *dev = NULL;
285 const fido_dev_info_t *di = NULL;
286 const char *path = NULL;
287 size_t ndevs = 0;
288 int r;
289
290 /* NOTE: initializes args_info. on error, frees args_info and calls exit() */
291 if (cmdline_parser(argc, argv, &args_info) != 0)
292 goto err;
293
294 if (args_info.help_given) {
295 cmdline_parser_print_help();
296 printf("\nReport bugs at <https://github.com/Yubico/pam-u2f>.\n");
297 exit_code = EXIT_SUCCESS;
298 goto err;
299 }
300
301 fido_init(args_info.debug_flag ? FIDO_DEBUG : 0);
302
303 if ((cred = prepare_cred(&args_info)) == NULL)
304 goto err;
305
306 devlist = fido_dev_info_new(64);
307 if (!devlist) {
308 fprintf(stderr, "error: fido_dev_info_new failed\n");
309 goto err;
310 }
311
312 r = fido_dev_info_manifest(devlist, 64, &ndevs);
313 if (r != FIDO_OK) {
314 fprintf(stderr, "Unable to discover device(s), %s (%d)\n", fido_strerr(r),
315 r);
316 goto err;
317 }
318
319 if (ndevs == 0) {
320 for (int i = 0; i < TIMEOUT; i += FREQUENCY) {
321 fprintf(stderr,
322 "\rNo U2F device available, please insert one now, you "
323 "have %2d seconds",
324 TIMEOUT - i);
325 fflush(stderr);
326 sleep(FREQUENCY);
327
328 r = fido_dev_info_manifest(devlist, 64, &ndevs);
329 if (r != FIDO_OK) {
330 fprintf(stderr, "\nUnable to discover device(s), %s (%d)",
331 fido_strerr(r), r);
332 goto err;
333 }
334
335 if (ndevs != 0) {
336 fprintf(stderr, "\nDevice found!\n");
337 break;
338 }
339 }
340 }
341
342 if (ndevs == 0) {
343 fprintf(stderr, "\rNo device found. Aborting. "
344 " \n");
345 goto err;
346 }
347
348 /* XXX loop over every device? */
349 dev = fido_dev_new();
350 if (!dev) {
351 fprintf(stderr, "fido_dev_new failed\n");
352 goto err;
353 }
354
355 di = fido_dev_info_ptr(devlist, 0);
356 if (!di) {
357 fprintf(stderr, "error: fido_dev_info_ptr returned NULL\n");
358 goto err;
359 }
360
361 if ((path = fido_dev_info_path(di)) == NULL) {
362 fprintf(stderr, "error: fido_dev_path returned NULL\n");
363 goto err;
364 }
365
366 r = fido_dev_open(dev, path);
367 if (r != FIDO_OK) {
368 fprintf(stderr, "error: fido_dev_open (%d) %s\n", r, fido_strerr(r));
369 goto err;
370 }
371
372 if (make_cred(path, dev, cred) != 0 || verify_cred(cred) != 0 ||
373 print_authfile_line(&args_info, cred) != 0)
374 goto err;
375
376 exit_code = EXIT_SUCCESS;
377
378 err:
379 if (dev != NULL)
380 fido_dev_close(dev);
381 fido_dev_info_free(&devlist, ndevs);
382 fido_cred_free(&cred);
383 fido_dev_free(&dev);
384
385 cmdline_parser_free(&args_info);
386
387 exit(exit_code);
388 }
389