pamu2fcfg.c revision 1.1.1.3 1 /*
2 * Copyright (C) 2014-2022 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 #define PIN_SET 0x01
11 #define PIN_UNSET 0x02
12 #define UV_SET 0x04
13 #define UV_UNSET 0x08
14 #define UV_REQD 0x10
15 #define UV_NOT_REQD 0x20
16
17 #include <fido.h>
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <getopt.h>
23 #include <unistd.h>
24 #include <sys/types.h>
25 #include <pwd.h>
26 #include <err.h>
27
28 #include "b64.h"
29 #include "util.h"
30
31 #include "openbsd-compat.h"
32
33 #ifndef FIDO_ERR_UV_BLOCKED /* XXX: compat libfido2 <1.5.0 */
34 #define FIDO_ERR_UV_BLOCKED 0x3c
35 #endif
36
37 struct args {
38 const char *appid;
39 const char *origin;
40 const char *type;
41 const char *username;
42 int resident;
43 int no_user_presence;
44 int pin_verification;
45 int user_verification;
46 int debug;
47 int verbose;
48 int nouser;
49 };
50
51 static fido_cred_t *prepare_cred(const struct args *const args) {
52 fido_cred_t *cred = NULL;
53 const char *appid = NULL;
54 const char *user = NULL;
55 struct passwd *passwd;
56 unsigned char userid[32];
57 unsigned char cdh[32];
58 char origin[BUFSIZE];
59 int type;
60 int ok = -1;
61 size_t n;
62 int r;
63
64 if ((cred = fido_cred_new()) == NULL) {
65 fprintf(stderr, "fido_cred_new failed\n");
66 goto err;
67 }
68
69 type = COSE_ES256; /* default */
70 if (args->type && !cose_type(args->type, &type)) {
71 fprintf(stderr, "Unknown COSE type '%s'.\n", args->type);
72 goto err;
73 }
74
75 if ((r = fido_cred_set_type(cred, type)) != FIDO_OK) {
76 fprintf(stderr, "error: fido_cred_set_type (%d): %s\n", r, fido_strerr(r));
77 goto err;
78 }
79
80 if (!random_bytes(cdh, sizeof(cdh))) {
81 fprintf(stderr, "random_bytes failed\n");
82 goto err;
83 }
84
85 if ((r = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh))) != FIDO_OK) {
86 fprintf(stderr, "error: fido_cred_set_clientdata_hash (%d): %s\n", r,
87 fido_strerr(r));
88 goto err;
89 }
90
91 if (args->origin) {
92 if (strlcpy(origin, args->origin, sizeof(origin)) >= sizeof(origin)) {
93 fprintf(stderr, "error: strlcpy failed\n");
94 goto err;
95 }
96 } else {
97 if ((n = strlcpy(origin, PAM_PREFIX, sizeof(origin))) >= sizeof(origin)) {
98 fprintf(stderr, "error: strlcpy failed\n");
99 goto err;
100 }
101 if (gethostname(origin + n, sizeof(origin) - n) == -1) {
102 perror("gethostname");
103 goto err;
104 }
105 }
106
107 if (args->appid) {
108 appid = args->appid;
109 } else {
110 appid = origin;
111 }
112
113 if (args->verbose) {
114 fprintf(stderr, "Setting origin to %s\n", origin);
115 fprintf(stderr, "Setting appid to %s\n", appid);
116 }
117
118 if ((r = fido_cred_set_rp(cred, origin, appid)) != FIDO_OK) {
119 fprintf(stderr, "error: fido_cred_set_rp (%d) %s\n", r, fido_strerr(r));
120 goto err;
121 }
122
123 if (args->username) {
124 user = args->username;
125 } else {
126 if ((passwd = getpwuid(getuid())) == NULL) {
127 perror("getpwuid");
128 goto err;
129 }
130 user = passwd->pw_name;
131 }
132
133 if (!random_bytes(userid, sizeof(userid))) {
134 fprintf(stderr, "random_bytes failed\n");
135 goto err;
136 }
137
138 if (args->verbose) {
139 fprintf(stderr, "Setting user to %s\n", user);
140 fprintf(stderr, "Setting user id to ");
141 for (size_t i = 0; i < sizeof(userid); i++)
142 fprintf(stderr, "%02x", userid[i]);
143 fprintf(stderr, "\n");
144 }
145
146 if ((r = fido_cred_set_user(cred, userid, sizeof(userid), user, user,
147 NULL)) != FIDO_OK) {
148 fprintf(stderr, "error: fido_cred_set_user (%d) %s\n", r, fido_strerr(r));
149 goto err;
150 }
151
152 if ((r = fido_cred_set_rk(cred, args->resident ? FIDO_OPT_TRUE
153 : FIDO_OPT_OMIT)) != FIDO_OK) {
154 fprintf(stderr, "error: fido_cred_set_rk (%d) %s\n", r, fido_strerr(r));
155 goto err;
156 }
157
158 if ((r = fido_cred_set_uv(cred, FIDO_OPT_OMIT)) != FIDO_OK) {
159 fprintf(stderr, "error: fido_cred_set_uv (%d) %s\n", r, fido_strerr(r));
160 goto err;
161 }
162
163 ok = 0;
164
165 err:
166 if (ok != 0) {
167 fido_cred_free(&cred);
168 }
169
170 return cred;
171 }
172
173 static int make_cred(const struct args *args, const char *path, fido_dev_t *dev,
174 fido_cred_t *cred, int devopts) {
175 char prompt[BUFSIZE];
176 char pin[BUFSIZE];
177 int n;
178 int r;
179
180 if (path == NULL || dev == NULL || cred == NULL) {
181 fprintf(stderr, "%s: args\n", __func__);
182 return -1;
183 }
184
185 /* Some form of UV required; built-in UV is available. */
186 if (args->user_verification || (devopts & (UV_SET | UV_NOT_REQD)) == UV_SET) {
187 if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK) {
188 fprintf(stderr, "error: fido_cred_set_uv: %s (%d)\n", fido_strerr(r), r);
189 return -1;
190 }
191 }
192
193 /* Let built-in UV have precedence over PIN. No UV also handled here. */
194 if (args->user_verification || !args->pin_verification) {
195 r = fido_dev_make_cred(dev, cred, NULL);
196 } else {
197 r = FIDO_ERR_PIN_REQUIRED;
198 }
199
200 /* Some form of UV required; built-in UV failed or is not available. */
201 if ((devopts & PIN_SET) &&
202 (r == FIDO_ERR_PIN_REQUIRED || r == FIDO_ERR_UV_BLOCKED ||
203 r == FIDO_ERR_PIN_BLOCKED)) {
204 n = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ", path);
205 if (n < 0 || (size_t) n >= sizeof(prompt)) {
206 fprintf(stderr, "error: snprintf prompt");
207 return -1;
208 }
209 if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF)) {
210 fprintf(stderr, "error: failed to read pin");
211 explicit_bzero(pin, sizeof(pin));
212 return -1;
213 }
214 r = fido_dev_make_cred(dev, cred, pin);
215 }
216 explicit_bzero(pin, sizeof(pin));
217
218 if (r != FIDO_OK) {
219 fprintf(stderr, "error: fido_dev_make_cred (%d) %s\n", r, fido_strerr(r));
220 return -1;
221 }
222
223 return 0;
224 }
225
226 static int verify_cred(const fido_cred_t *const cred) {
227 int r;
228
229 if (cred == NULL) {
230 fprintf(stderr, "%s: args\n", __func__);
231 return -1;
232 }
233
234 if (fido_cred_x5c_ptr(cred) == NULL) {
235 if ((r = fido_cred_verify_self(cred)) != FIDO_OK) {
236 fprintf(stderr, "error: fido_cred_verify_self (%d) %s\n", r,
237 fido_strerr(r));
238 return -1;
239 }
240 } else {
241 if ((r = fido_cred_verify(cred)) != FIDO_OK) {
242 fprintf(stderr, "error: fido_cred_verify (%d) %s\n", r, fido_strerr(r));
243 return -1;
244 }
245 }
246
247 return 0;
248 }
249
250 static int print_authfile_line(const struct args *const args,
251 const fido_cred_t *const cred) {
252 const unsigned char *kh = NULL;
253 const unsigned char *pk = NULL;
254 const char *user = NULL;
255 char *b64_kh = NULL;
256 char *b64_pk = NULL;
257 size_t kh_len;
258 size_t pk_len;
259 int ok = -1;
260
261 if ((kh = fido_cred_id_ptr(cred)) == NULL) {
262 fprintf(stderr, "error: fido_cred_id_ptr returned NULL\n");
263 goto err;
264 }
265
266 if ((kh_len = fido_cred_id_len(cred)) == 0) {
267 fprintf(stderr, "error: fido_cred_id_len returned 0\n");
268 goto err;
269 }
270
271 if ((pk = fido_cred_pubkey_ptr(cred)) == NULL) {
272 fprintf(stderr, "error: fido_cred_pubkey_ptr returned NULL\n");
273 goto err;
274 }
275
276 if ((pk_len = fido_cred_pubkey_len(cred)) == 0) {
277 fprintf(stderr, "error: fido_cred_pubkey_len returned 0\n");
278 goto err;
279 }
280
281 if (!b64_encode(kh, kh_len, &b64_kh)) {
282 fprintf(stderr, "error: failed to encode key handle\n");
283 goto err;
284 }
285
286 if (!b64_encode(pk, pk_len, &b64_pk)) {
287 fprintf(stderr, "error: failed to encode public key\n");
288 goto err;
289 }
290
291 if (!args->nouser) {
292 if ((user = fido_cred_user_name(cred)) == NULL) {
293 fprintf(stderr, "error: fido_cred_user_name returned NULL\n");
294 goto err;
295 }
296 printf("%s", user);
297 }
298
299 printf(":%s,%s,%s,%s%s%s", args->resident ? "*" : b64_kh, b64_pk,
300 cose_string(fido_cred_type(cred)),
301 !args->no_user_presence ? "+presence" : "",
302 args->user_verification ? "+verification" : "",
303 args->pin_verification ? "+pin" : "");
304
305 ok = 0;
306
307 err:
308 free(b64_kh);
309 free(b64_pk);
310
311 return ok;
312 }
313
314 static int get_device_options(fido_dev_t *dev, int *devopts) {
315 char *const *opts;
316 const bool *vals;
317 fido_cbor_info_t *info;
318 int r;
319
320 *devopts = 0;
321
322 if (!fido_dev_is_fido2(dev))
323 return 0;
324
325 if ((info = fido_cbor_info_new()) == NULL) {
326 fprintf(stderr, "fido_cbor_info_new failed\n");
327 return -1;
328 }
329 if ((r = fido_dev_get_cbor_info(dev, info)) != FIDO_OK) {
330 fprintf(stderr, "fido_dev_get_cbor_info: %s (%d)\n", fido_strerr(r), r);
331 fido_cbor_info_free(&info);
332 return -1;
333 }
334
335 opts = fido_cbor_info_options_name_ptr(info);
336 vals = fido_cbor_info_options_value_ptr(info);
337 for (size_t i = 0; i < fido_cbor_info_options_len(info); i++) {
338 if (strcmp(opts[i], "clientPin") == 0) {
339 *devopts |= vals[i] ? PIN_SET : PIN_UNSET;
340 } else if (strcmp(opts[i], "uv") == 0) {
341 *devopts |= vals[i] ? UV_SET : UV_UNSET;
342 } else if (strcmp(opts[i], "makeCredUvNotRqd") == 0) {
343 *devopts |= vals[i] ? UV_NOT_REQD : UV_REQD;
344 }
345 }
346
347 fido_cbor_info_free(&info);
348
349 return 0;
350 }
351
352 static void parse_args(int argc, char *argv[], struct args *args) {
353 int c;
354 enum {
355 OPT_VERSION = 0x100,
356 };
357 /* clang-format off */
358 static const struct option options[] = {
359 { "help", no_argument, NULL, 'h' },
360 { "version", no_argument, NULL, OPT_VERSION },
361 { "origin", required_argument, NULL, 'o' },
362 { "appid", required_argument, NULL, 'i' },
363 { "type", required_argument, NULL, 't' },
364 { "resident", no_argument, NULL, 'r' },
365 { "no-user-presence", no_argument, NULL, 'P' },
366 { "pin-verification", no_argument, NULL, 'N' },
367 { "user-verification", no_argument, NULL, 'V' },
368 { "debug", no_argument, NULL, 'd' },
369 { "verbose", no_argument, NULL, 'v' },
370 { "username", required_argument, NULL, 'u' },
371 { "nouser", no_argument, NULL, 'n' },
372 { 0, 0, 0, 0 }
373 };
374 const char *usage =
375 "Usage: pamu2fcfg [OPTION]...\n"
376 "Perform a FIDO2/U2F registration operation and print a configuration line that\n"
377 "can be used with the pam_u2f module.\n"
378 "\n"
379 " -h, --help Print help and exit\n"
380 " --version Print version and exit\n"
381 " -o, --origin=STRING Relying party ID to use during registration,\n"
382 " defaults to pam://hostname\n"
383 " -i, --appid=STRING Relying party name to use during registration,\n"
384 " defaults to the value of origin\n"
385 " -t, --type=STRING COSE type to use during registration (ES256, EDDSA,\n"
386 " or RS256), defaults to ES256\n"
387 " -r, --resident Generate a resident (discoverable) credential\n"
388 " -P, --no-user-presence Allow the credential to be used without ensuring the\n"
389 " user's presence\n"
390 " -N, --pin-verification Require PIN verification during authentication\n"
391 " -V, --user-verification Require user verification during authentication\n"
392 " -d, --debug Print debug information\n"
393 " -v, --verbose Print information about chosen origin and appid\n"
394 " -u, --username=STRING The name of the user registering the device,\n"
395 " defaults to the current user name\n"
396 " -n, --nouser Print only registration information (key handle,\n"
397 " public key, and options), useful for appending\n"
398 "\n"
399 "Report bugs at <" PACKAGE_BUGREPORT ">.\n";
400 /* clang-format on */
401
402 while ((c = getopt_long(argc, argv, "ho:i:t:rPNVdvu:n", options, NULL)) !=
403 -1) {
404 switch (c) {
405 case 'h':
406 printf("%s", usage);
407 exit(EXIT_SUCCESS);
408 case 'o':
409 args->origin = optarg;
410 break;
411 case 'i':
412 args->appid = optarg;
413 break;
414 case 't':
415 args->type = optarg;
416 break;
417 case 'u':
418 args->username = optarg;
419 break;
420 case 'r':
421 args->resident = 1;
422 break;
423 case 'P':
424 args->no_user_presence = 1;
425 break;
426 case 'N':
427 args->pin_verification = 1;
428 break;
429 case 'V':
430 args->user_verification = 1;
431 break;
432 case 'd':
433 args->debug = 1;
434 break;
435 case 'v':
436 args->verbose = 1;
437 break;
438 case 'n':
439 args->nouser = 1;
440 break;
441 case OPT_VERSION:
442 printf("pamu2fcfg " PACKAGE_VERSION "\n");
443 exit(EXIT_SUCCESS);
444 case '?':
445 exit(EXIT_FAILURE);
446 default:
447 errx(EXIT_FAILURE, "unknown option 0x%x", c);
448 }
449 }
450
451 if (optind != argc)
452 errx(EXIT_FAILURE, "unsupported positional argument(s)");
453 }
454
455 int main(int argc, char *argv[]) {
456 int exit_code = EXIT_FAILURE;
457 struct args args = {0};
458 fido_cred_t *cred = NULL;
459 fido_dev_info_t *devlist = NULL;
460 fido_dev_t *dev = NULL;
461 const fido_dev_info_t *di = NULL;
462 const char *path = NULL;
463 size_t ndevs = 0;
464 int devopts = 0;
465 int r;
466
467 parse_args(argc, argv, &args);
468 fido_init(args.debug ? FIDO_DEBUG : 0);
469
470 devlist = fido_dev_info_new(DEVLIST_LEN);
471 if (!devlist) {
472 fprintf(stderr, "error: fido_dev_info_new failed\n");
473 goto err;
474 }
475
476 r = fido_dev_info_manifest(devlist, DEVLIST_LEN, &ndevs);
477 if (r != FIDO_OK) {
478 fprintf(stderr, "Unable to discover device(s), %s (%d)\n", fido_strerr(r),
479 r);
480 goto err;
481 }
482
483 if (ndevs == 0) {
484 for (int i = 0; i < TIMEOUT; i += FREQUENCY) {
485 fprintf(stderr,
486 "\rNo U2F device available, please insert one now, you "
487 "have %2d seconds",
488 TIMEOUT - i);
489 fflush(stderr);
490 sleep(FREQUENCY);
491
492 r = fido_dev_info_manifest(devlist, DEVLIST_LEN, &ndevs);
493 if (r != FIDO_OK) {
494 fprintf(stderr, "\nUnable to discover device(s), %s (%d)",
495 fido_strerr(r), r);
496 goto err;
497 }
498
499 if (ndevs != 0) {
500 fprintf(stderr, "\nDevice found!\n");
501 break;
502 }
503 }
504 }
505
506 if (ndevs == 0) {
507 fprintf(stderr, "\rNo device found. Aborting. "
508 " \n");
509 goto err;
510 }
511
512 /* XXX loop over every device? */
513 dev = fido_dev_new();
514 if (!dev) {
515 fprintf(stderr, "fido_dev_new failed\n");
516 goto err;
517 }
518
519 di = fido_dev_info_ptr(devlist, 0);
520 if (!di) {
521 fprintf(stderr, "error: fido_dev_info_ptr returned NULL\n");
522 goto err;
523 }
524
525 if ((path = fido_dev_info_path(di)) == NULL) {
526 fprintf(stderr, "error: fido_dev_path returned NULL\n");
527 goto err;
528 }
529
530 r = fido_dev_open(dev, path);
531 if (r != FIDO_OK) {
532 fprintf(stderr, "error: fido_dev_open (%d) %s\n", r, fido_strerr(r));
533 goto err;
534 }
535
536 if (get_device_options(dev, &devopts) != 0) {
537 goto err;
538 }
539 if (args.pin_verification && !(devopts & PIN_SET)) {
540 warnx("%s", devopts & PIN_UNSET ? "device has no PIN"
541 : "device does not support PIN");
542 goto err;
543 }
544 if (args.user_verification && !(devopts & UV_SET)) {
545 warnx("%s", devopts & UV_UNSET
546 ? "device has no built-in user verification configured"
547 : "device does not support built-in user verification");
548 goto err;
549 }
550 if ((devopts & (UV_REQD | PIN_SET | UV_SET)) == UV_REQD) {
551 warnx("%s", "some form of user verification required but none configured");
552 goto err;
553 }
554
555 if ((cred = prepare_cred(&args)) == NULL)
556 goto err;
557
558 if (make_cred(&args, path, dev, cred, devopts) != 0 ||
559 verify_cred(cred) != 0 || print_authfile_line(&args, cred) != 0)
560 goto err;
561
562 exit_code = EXIT_SUCCESS;
563
564 err:
565 if (dev != NULL)
566 fido_dev_close(dev);
567 fido_dev_info_free(&devlist, ndevs);
568 fido_cred_free(&cred);
569 fido_dev_free(&dev);
570
571 exit(exit_code);
572 }
573