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\n", 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