diff options
Diffstat (limited to 'auth2-pubkey.c')
-rw-r--r-- | auth2-pubkey.c | 532 |
1 files changed, 365 insertions, 167 deletions
diff --git a/auth2-pubkey.c b/auth2-pubkey.c index 8fb7ffe71..8024b1d6a 100644 --- a/auth2-pubkey.c +++ b/auth2-pubkey.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: auth2-pubkey.c,v 1.76 2018/02/07 22:52:45 dtucker Exp $ */ | 1 | /* $OpenBSD: auth2-pubkey.c,v 1.77 2018/03/03 03:15:51 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
4 | * | 4 | * |
@@ -88,6 +88,7 @@ static int | |||
88 | userauth_pubkey(struct ssh *ssh) | 88 | userauth_pubkey(struct ssh *ssh) |
89 | { | 89 | { |
90 | Authctxt *authctxt = ssh->authctxt; | 90 | Authctxt *authctxt = ssh->authctxt; |
91 | struct passwd *pw = authctxt->pw; | ||
91 | struct sshbuf *b; | 92 | struct sshbuf *b; |
92 | struct sshkey *key = NULL; | 93 | struct sshkey *key = NULL; |
93 | char *pkalg, *userstyle = NULL, *key_s = NULL, *ca_s = NULL; | 94 | char *pkalg, *userstyle = NULL, *key_s = NULL, *ca_s = NULL; |
@@ -95,6 +96,7 @@ userauth_pubkey(struct ssh *ssh) | |||
95 | size_t blen, slen; | 96 | size_t blen, slen; |
96 | int r, pktype; | 97 | int r, pktype; |
97 | int authenticated = 0; | 98 | int authenticated = 0; |
99 | struct sshauthopt *authopts = NULL; | ||
98 | 100 | ||
99 | if (!authctxt->valid) { | 101 | if (!authctxt->valid) { |
100 | debug2("%s: disabled because of invalid user", __func__); | 102 | debug2("%s: disabled because of invalid user", __func__); |
@@ -185,7 +187,7 @@ userauth_pubkey(struct ssh *ssh) | |||
185 | 187 | ||
186 | /* test for correct signature */ | 188 | /* test for correct signature */ |
187 | authenticated = 0; | 189 | authenticated = 0; |
188 | if (PRIVSEP(user_key_allowed(authctxt->pw, key, 1)) && | 190 | if (PRIVSEP(user_key_allowed(ssh, pw, key, 1, &authopts)) && |
189 | PRIVSEP(sshkey_verify(key, sig, slen, sshbuf_ptr(b), | 191 | PRIVSEP(sshkey_verify(key, sig, slen, sshbuf_ptr(b), |
190 | sshbuf_len(b), NULL, ssh->compat)) == 0) { | 192 | sshbuf_len(b), NULL, ssh->compat)) == 0) { |
191 | authenticated = 1; | 193 | authenticated = 1; |
@@ -210,7 +212,7 @@ userauth_pubkey(struct ssh *ssh) | |||
210 | * if a user is not allowed to login. is this an | 212 | * if a user is not allowed to login. is this an |
211 | * issue? -markus | 213 | * issue? -markus |
212 | */ | 214 | */ |
213 | if (PRIVSEP(user_key_allowed(authctxt->pw, key, 0))) { | 215 | if (PRIVSEP(user_key_allowed(ssh, pw, key, 0, NULL))) { |
214 | if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_PK_OK)) | 216 | if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_PK_OK)) |
215 | != 0 || | 217 | != 0 || |
216 | (r = sshpkt_put_cstring(ssh, pkalg)) != 0 || | 218 | (r = sshpkt_put_cstring(ssh, pkalg)) != 0 || |
@@ -221,10 +223,14 @@ userauth_pubkey(struct ssh *ssh) | |||
221 | authctxt->postponed = 1; | 223 | authctxt->postponed = 1; |
222 | } | 224 | } |
223 | } | 225 | } |
224 | if (authenticated != 1) | ||
225 | auth_clear_options(); | ||
226 | done: | 226 | done: |
227 | if (authenticated == 1 && auth_activate_options(ssh, authopts) != 0) { | ||
228 | debug("%s: key options inconsistent with existing", __func__); | ||
229 | authenticated = 0; | ||
230 | } | ||
227 | debug2("%s: authenticated %d pkalg %s", __func__, authenticated, pkalg); | 231 | debug2("%s: authenticated %d pkalg %s", __func__, authenticated, pkalg); |
232 | |||
233 | sshauthopt_free(authopts); | ||
228 | sshkey_free(key); | 234 | sshkey_free(key); |
229 | free(userstyle); | 235 | free(userstyle); |
230 | free(pkalg); | 236 | free(pkalg); |
@@ -254,18 +260,77 @@ match_principals_option(const char *principal_list, struct sshkey_cert *cert) | |||
254 | return 0; | 260 | return 0; |
255 | } | 261 | } |
256 | 262 | ||
263 | /* | ||
264 | * Process a single authorized_principals format line. Returns 0 and sets | ||
265 | * authoptsp is principal is authorised, -1 otherwise. "loc" is used as a | ||
266 | * log preamble for file/line information. | ||
267 | */ | ||
268 | static int | ||
269 | check_principals_line(struct ssh *ssh, char *cp, const struct sshkey_cert *cert, | ||
270 | const char *loc, struct sshauthopt **authoptsp) | ||
271 | { | ||
272 | u_int i, found = 0; | ||
273 | char *ep, *line_opts; | ||
274 | const char *reason = NULL; | ||
275 | struct sshauthopt *opts = NULL; | ||
276 | |||
277 | if (authoptsp != NULL) | ||
278 | *authoptsp = NULL; | ||
279 | |||
280 | /* Trim trailing whitespace. */ | ||
281 | ep = cp + strlen(cp) - 1; | ||
282 | while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t')) | ||
283 | *ep-- = '\0'; | ||
284 | |||
285 | /* | ||
286 | * If the line has internal whitespace then assume it has | ||
287 | * key options. | ||
288 | */ | ||
289 | line_opts = NULL; | ||
290 | if ((ep = strrchr(cp, ' ')) != NULL || | ||
291 | (ep = strrchr(cp, '\t')) != NULL) { | ||
292 | for (; *ep == ' ' || *ep == '\t'; ep++) | ||
293 | ; | ||
294 | line_opts = cp; | ||
295 | cp = ep; | ||
296 | } | ||
297 | if ((opts = sshauthopt_parse(line_opts, &reason)) == NULL) { | ||
298 | debug("%s: bad principals options: %s", loc, reason); | ||
299 | auth_debug_add("%s: bad principals options: %s", loc, reason); | ||
300 | return -1; | ||
301 | } | ||
302 | /* Check principals in cert against those on line */ | ||
303 | for (i = 0; i < cert->nprincipals; i++) { | ||
304 | if (strcmp(cp, cert->principals[i]) != 0) | ||
305 | continue; | ||
306 | debug3("%s: matched principal \"%.100s\"", | ||
307 | loc, cert->principals[i]); | ||
308 | found = 1; | ||
309 | } | ||
310 | if (found && authoptsp != NULL) { | ||
311 | *authoptsp = opts; | ||
312 | opts = NULL; | ||
313 | } | ||
314 | sshauthopt_free(opts); | ||
315 | return found ? 0 : -1; | ||
316 | } | ||
317 | |||
257 | static int | 318 | static int |
258 | process_principals(FILE *f, const char *file, struct passwd *pw, | 319 | process_principals(struct ssh *ssh, FILE *f, const char *file, |
259 | const struct sshkey_cert *cert) | 320 | const struct sshkey_cert *cert, struct sshauthopt **authoptsp) |
260 | { | 321 | { |
261 | char line[SSH_MAX_PUBKEY_BYTES], *cp, *ep, *line_opts; | 322 | char loc[256], line[SSH_MAX_PUBKEY_BYTES], *cp, *ep; |
262 | u_long linenum = 0; | 323 | u_long linenum = 0; |
263 | u_int i, found_principal = 0; | 324 | u_int found_principal = 0; |
325 | |||
326 | if (authoptsp != NULL) | ||
327 | *authoptsp = NULL; | ||
264 | 328 | ||
265 | while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) { | 329 | while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) { |
266 | /* Always consume entire input */ | 330 | /* Always consume entire input */ |
267 | if (found_principal) | 331 | if (found_principal) |
268 | continue; | 332 | continue; |
333 | |||
269 | /* Skip leading whitespace. */ | 334 | /* Skip leading whitespace. */ |
270 | for (cp = line; *cp == ' ' || *cp == '\t'; cp++) | 335 | for (cp = line; *cp == ' ' || *cp == '\t'; cp++) |
271 | ; | 336 | ; |
@@ -274,50 +339,33 @@ process_principals(FILE *f, const char *file, struct passwd *pw, | |||
274 | *ep = '\0'; | 339 | *ep = '\0'; |
275 | if (!*cp || *cp == '\n') | 340 | if (!*cp || *cp == '\n') |
276 | continue; | 341 | continue; |
277 | /* Trim trailing whitespace. */ | 342 | |
278 | ep = cp + strlen(cp) - 1; | 343 | snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum); |
279 | while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t')) | 344 | if (check_principals_line(ssh, cp, cert, loc, authoptsp) == 0) |
280 | *ep-- = '\0'; | 345 | found_principal = 1; |
281 | /* | ||
282 | * If the line has internal whitespace then assume it has | ||
283 | * key options. | ||
284 | */ | ||
285 | line_opts = NULL; | ||
286 | if ((ep = strrchr(cp, ' ')) != NULL || | ||
287 | (ep = strrchr(cp, '\t')) != NULL) { | ||
288 | for (; *ep == ' ' || *ep == '\t'; ep++) | ||
289 | ; | ||
290 | line_opts = cp; | ||
291 | cp = ep; | ||
292 | } | ||
293 | for (i = 0; i < cert->nprincipals; i++) { | ||
294 | if (strcmp(cp, cert->principals[i]) == 0) { | ||
295 | debug3("%s:%lu: matched principal \"%.100s\"", | ||
296 | file, linenum, cert->principals[i]); | ||
297 | if (auth_parse_options(pw, line_opts, | ||
298 | file, linenum) != 1) | ||
299 | continue; | ||
300 | found_principal = 1; | ||
301 | continue; | ||
302 | } | ||
303 | } | ||
304 | } | 346 | } |
305 | return found_principal; | 347 | return found_principal; |
306 | } | 348 | } |
307 | 349 | ||
350 | /* XXX remove pw args here and elsewhere once ssh->authctxt is guaranteed */ | ||
351 | |||
308 | static int | 352 | static int |
309 | match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert) | 353 | match_principals_file(struct ssh *ssh, struct passwd *pw, char *file, |
354 | struct sshkey_cert *cert, struct sshauthopt **authoptsp) | ||
310 | { | 355 | { |
311 | FILE *f; | 356 | FILE *f; |
312 | int success; | 357 | int success; |
313 | 358 | ||
359 | if (authoptsp != NULL) | ||
360 | *authoptsp = NULL; | ||
361 | |||
314 | temporarily_use_uid(pw); | 362 | temporarily_use_uid(pw); |
315 | debug("trying authorized principals file %s", file); | 363 | debug("trying authorized principals file %s", file); |
316 | if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) { | 364 | if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) { |
317 | restore_uid(); | 365 | restore_uid(); |
318 | return 0; | 366 | return 0; |
319 | } | 367 | } |
320 | success = process_principals(f, file, pw, cert); | 368 | success = process_principals(ssh, f, file, cert, authoptsp); |
321 | fclose(f); | 369 | fclose(f); |
322 | restore_uid(); | 370 | restore_uid(); |
323 | return success; | 371 | return success; |
@@ -328,12 +376,13 @@ match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert) | |||
328 | * returns 1 if the principal is allowed or 0 otherwise. | 376 | * returns 1 if the principal is allowed or 0 otherwise. |
329 | */ | 377 | */ |
330 | static int | 378 | static int |
331 | match_principals_command(struct passwd *user_pw, const struct sshkey *key) | 379 | match_principals_command(struct ssh *ssh, struct passwd *user_pw, |
380 | const struct sshkey *key, struct sshauthopt **authoptsp) | ||
332 | { | 381 | { |
382 | struct passwd *runas_pw = NULL; | ||
333 | const struct sshkey_cert *cert = key->cert; | 383 | const struct sshkey_cert *cert = key->cert; |
334 | FILE *f = NULL; | 384 | FILE *f = NULL; |
335 | int r, ok, found_principal = 0; | 385 | int r, ok, found_principal = 0; |
336 | struct passwd *pw; | ||
337 | int i, ac = 0, uid_swapped = 0; | 386 | int i, ac = 0, uid_swapped = 0; |
338 | pid_t pid; | 387 | pid_t pid; |
339 | char *tmp, *username = NULL, *command = NULL, **av = NULL; | 388 | char *tmp, *username = NULL, *command = NULL, **av = NULL; |
@@ -341,6 +390,8 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key) | |||
341 | char serial_s[16]; | 390 | char serial_s[16]; |
342 | void (*osigchld)(int); | 391 | void (*osigchld)(int); |
343 | 392 | ||
393 | if (authoptsp != NULL) | ||
394 | *authoptsp = NULL; | ||
344 | if (options.authorized_principals_command == NULL) | 395 | if (options.authorized_principals_command == NULL) |
345 | return 0; | 396 | return 0; |
346 | if (options.authorized_principals_command_user == NULL) { | 397 | if (options.authorized_principals_command_user == NULL) { |
@@ -358,8 +409,8 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key) | |||
358 | /* Prepare and verify the user for the command */ | 409 | /* Prepare and verify the user for the command */ |
359 | username = percent_expand(options.authorized_principals_command_user, | 410 | username = percent_expand(options.authorized_principals_command_user, |
360 | "u", user_pw->pw_name, (char *)NULL); | 411 | "u", user_pw->pw_name, (char *)NULL); |
361 | pw = getpwnam(username); | 412 | runas_pw = getpwnam(username); |
362 | if (pw == NULL) { | 413 | if (runas_pw == NULL) { |
363 | error("AuthorizedPrincipalsCommandUser \"%s\" not found: %s", | 414 | error("AuthorizedPrincipalsCommandUser \"%s\" not found: %s", |
364 | username, strerror(errno)); | 415 | username, strerror(errno)); |
365 | goto out; | 416 | goto out; |
@@ -417,15 +468,15 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key) | |||
417 | /* Prepare a printable command for logs, etc. */ | 468 | /* Prepare a printable command for logs, etc. */ |
418 | command = argv_assemble(ac, av); | 469 | command = argv_assemble(ac, av); |
419 | 470 | ||
420 | if ((pid = subprocess("AuthorizedPrincipalsCommand", pw, command, | 471 | if ((pid = subprocess("AuthorizedPrincipalsCommand", runas_pw, command, |
421 | ac, av, &f, | 472 | ac, av, &f, |
422 | SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD)) == 0) | 473 | SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD)) == 0) |
423 | goto out; | 474 | goto out; |
424 | 475 | ||
425 | uid_swapped = 1; | 476 | uid_swapped = 1; |
426 | temporarily_use_uid(pw); | 477 | temporarily_use_uid(runas_pw); |
427 | 478 | ||
428 | ok = process_principals(f, "(command)", pw, cert); | 479 | ok = process_principals(ssh, f, "(command)", cert, authoptsp); |
429 | 480 | ||
430 | fclose(f); | 481 | fclose(f); |
431 | f = NULL; | 482 | f = NULL; |
@@ -452,130 +503,225 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key) | |||
452 | free(keytext); | 503 | free(keytext); |
453 | return found_principal; | 504 | return found_principal; |
454 | } | 505 | } |
506 | |||
507 | static void | ||
508 | skip_space(char **cpp) | ||
509 | { | ||
510 | char *cp; | ||
511 | |||
512 | for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) | ||
513 | ; | ||
514 | *cpp = cp; | ||
515 | } | ||
516 | |||
517 | /* | ||
518 | * Advanced *cpp past the end of key options, defined as the first unquoted | ||
519 | * whitespace character. Returns 0 on success or -1 on failure (e.g. | ||
520 | * unterminated quotes). | ||
521 | */ | ||
522 | static int | ||
523 | advance_past_options(char **cpp) | ||
524 | { | ||
525 | char *cp = *cpp; | ||
526 | int quoted = 0; | ||
527 | |||
528 | for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) { | ||
529 | if (*cp == '\\' && cp[1] == '"') | ||
530 | cp++; /* Skip both */ | ||
531 | else if (*cp == '"') | ||
532 | quoted = !quoted; | ||
533 | } | ||
534 | *cpp = cp; | ||
535 | /* return failure for unterminated quotes */ | ||
536 | return (*cp == '\0' && quoted) ? -1 : 0; | ||
537 | } | ||
538 | |||
539 | /* | ||
540 | * Check a single line of an authorized_keys-format file. Returns 0 if key | ||
541 | * matches, -1 otherwise. Will return key/cert options via *authoptsp | ||
542 | * on success. "loc" is used as file/line location in log messages. | ||
543 | */ | ||
544 | static int | ||
545 | check_authkey_line(struct ssh *ssh, struct passwd *pw, struct sshkey *key, | ||
546 | char *cp, const char *loc, struct sshauthopt **authoptsp) | ||
547 | { | ||
548 | int want_keytype = sshkey_is_cert(key) ? KEY_UNSPEC : key->type; | ||
549 | struct sshkey *found = NULL; | ||
550 | struct sshauthopt *keyopts = NULL, *certopts = NULL, *finalopts = NULL; | ||
551 | char *key_options = NULL, *fp = NULL; | ||
552 | const char *reason = NULL; | ||
553 | int ret = -1; | ||
554 | |||
555 | if (authoptsp != NULL) | ||
556 | *authoptsp = NULL; | ||
557 | |||
558 | if ((found = sshkey_new(want_keytype)) == NULL) { | ||
559 | debug3("%s: keytype %d failed", __func__, want_keytype); | ||
560 | goto out; | ||
561 | } | ||
562 | |||
563 | /* XXX djm: peek at key type in line and skip if unwanted */ | ||
564 | |||
565 | if (sshkey_read(found, &cp) != 0) { | ||
566 | /* no key? check for options */ | ||
567 | debug2("%s: check options: '%s'", loc, cp); | ||
568 | key_options = cp; | ||
569 | if (advance_past_options(&cp) != 0) { | ||
570 | reason = "invalid key option string"; | ||
571 | goto fail_reason; | ||
572 | } | ||
573 | skip_space(&cp); | ||
574 | if (sshkey_read(found, &cp) != 0) { | ||
575 | /* still no key? advance to next line*/ | ||
576 | debug2("%s: advance: '%s'", loc, cp); | ||
577 | goto out; | ||
578 | } | ||
579 | } | ||
580 | /* Parse key options now; we need to know if this is a CA key */ | ||
581 | if ((keyopts = sshauthopt_parse(key_options, &reason)) == NULL) { | ||
582 | debug("%s: bad key options: %s", loc, reason); | ||
583 | auth_debug_add("%s: bad key options: %s", loc, reason); | ||
584 | goto out; | ||
585 | } | ||
586 | /* Ignore keys that don't match or incorrectly marked as CAs */ | ||
587 | if (sshkey_is_cert(key)) { | ||
588 | /* Certificate; check signature key against CA */ | ||
589 | if (!sshkey_equal(found, key->cert->signature_key) || | ||
590 | !keyopts->cert_authority) | ||
591 | goto out; | ||
592 | } else { | ||
593 | /* Plain key: check it against key found in file */ | ||
594 | if (!sshkey_equal(found, key) || keyopts->cert_authority) | ||
595 | goto out; | ||
596 | } | ||
597 | |||
598 | /* We have a candidate key, perform authorisation checks */ | ||
599 | if ((fp = sshkey_fingerprint(found, | ||
600 | options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) | ||
601 | fatal("%s: fingerprint failed", __func__); | ||
602 | |||
603 | debug("%s: matching %s found: %s %s", loc, | ||
604 | sshkey_is_cert(key) ? "CA" : "key", sshkey_type(found), fp); | ||
605 | |||
606 | if (auth_authorise_keyopts(ssh, pw, keyopts, | ||
607 | sshkey_is_cert(key), loc) != 0) { | ||
608 | reason = "Refused by key options"; | ||
609 | goto fail_reason; | ||
610 | } | ||
611 | /* That's all we need for plain keys. */ | ||
612 | if (!sshkey_is_cert(key)) { | ||
613 | verbose("Accepted key %s %s found at %s", | ||
614 | sshkey_type(found), fp, loc); | ||
615 | finalopts = keyopts; | ||
616 | keyopts = NULL; | ||
617 | goto success; | ||
618 | } | ||
619 | |||
620 | /* | ||
621 | * Additional authorisation for certificates. | ||
622 | */ | ||
623 | |||
624 | /* Parse and check options present in certificate */ | ||
625 | if ((certopts = sshauthopt_from_cert(key)) == NULL) { | ||
626 | reason = "Invalid certificate options"; | ||
627 | goto fail_reason; | ||
628 | } | ||
629 | if (auth_authorise_keyopts(ssh, pw, certopts, 0, loc) != 0) { | ||
630 | reason = "Refused by certificate options"; | ||
631 | goto fail_reason; | ||
632 | } | ||
633 | if ((finalopts = sshauthopt_merge(keyopts, certopts, &reason)) == NULL) | ||
634 | goto fail_reason; | ||
635 | |||
636 | /* | ||
637 | * If the user has specified a list of principals as | ||
638 | * a key option, then prefer that list to matching | ||
639 | * their username in the certificate principals list. | ||
640 | */ | ||
641 | if (keyopts->cert_principals != NULL && | ||
642 | !match_principals_option(keyopts->cert_principals, key->cert)) { | ||
643 | reason = "Certificate does not contain an authorized principal"; | ||
644 | goto fail_reason; | ||
645 | } | ||
646 | if (sshkey_cert_check_authority(key, 0, 0, | ||
647 | keyopts->cert_principals == NULL ? pw->pw_name : NULL, &reason) != 0) | ||
648 | goto fail_reason; | ||
649 | |||
650 | verbose("Accepted certificate ID \"%s\" (serial %llu) " | ||
651 | "signed by CA %s %s found at %s", | ||
652 | key->cert->key_id, | ||
653 | (unsigned long long)key->cert->serial, | ||
654 | sshkey_type(found), fp, loc); | ||
655 | |||
656 | success: | ||
657 | if (finalopts == NULL) | ||
658 | fatal("%s: internal error: missing options", __func__); | ||
659 | if (authoptsp != NULL) { | ||
660 | *authoptsp = finalopts; | ||
661 | finalopts = NULL; | ||
662 | } | ||
663 | /* success */ | ||
664 | ret = 0; | ||
665 | goto out; | ||
666 | |||
667 | fail_reason: | ||
668 | error("%s", reason); | ||
669 | auth_debug_add("%s", reason); | ||
670 | out: | ||
671 | free(fp); | ||
672 | sshauthopt_free(keyopts); | ||
673 | sshauthopt_free(certopts); | ||
674 | sshauthopt_free(finalopts); | ||
675 | sshkey_free(found); | ||
676 | return ret; | ||
677 | } | ||
678 | |||
455 | /* | 679 | /* |
456 | * Checks whether key is allowed in authorized_keys-format file, | 680 | * Checks whether key is allowed in authorized_keys-format file, |
457 | * returns 1 if the key is allowed or 0 otherwise. | 681 | * returns 1 if the key is allowed or 0 otherwise. |
458 | */ | 682 | */ |
459 | static int | 683 | static int |
460 | check_authkeys_file(FILE *f, char *file, struct sshkey *key, struct passwd *pw) | 684 | check_authkeys_file(struct ssh *ssh, struct passwd *pw, FILE *f, |
685 | char *file, struct sshkey *key, struct sshauthopt **authoptsp) | ||
461 | { | 686 | { |
462 | char line[SSH_MAX_PUBKEY_BYTES]; | 687 | char *cp, line[SSH_MAX_PUBKEY_BYTES], loc[256]; |
463 | int found_key = 0; | 688 | int found_key = 0; |
464 | u_long linenum = 0; | 689 | u_long linenum = 0; |
465 | struct sshkey *found = NULL; | ||
466 | 690 | ||
467 | while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) { | 691 | if (authoptsp != NULL) |
468 | char *cp, *key_options = NULL, *fp = NULL; | 692 | *authoptsp = NULL; |
469 | const char *reason = NULL; | ||
470 | 693 | ||
694 | while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) { | ||
471 | /* Always consume entire file */ | 695 | /* Always consume entire file */ |
472 | if (found_key) | 696 | if (found_key) |
473 | continue; | 697 | continue; |
474 | sshkey_free(found); | ||
475 | found = sshkey_new(sshkey_is_cert(key) ? KEY_UNSPEC : key->type); | ||
476 | if (found == NULL) | ||
477 | goto done; | ||
478 | auth_clear_options(); | ||
479 | 698 | ||
480 | /* Skip leading whitespace, empty and comment lines. */ | 699 | /* Skip leading whitespace, empty and comment lines. */ |
481 | for (cp = line; *cp == ' ' || *cp == '\t'; cp++) | 700 | cp = line; |
482 | ; | 701 | skip_space(&cp); |
483 | if (!*cp || *cp == '\n' || *cp == '#') | 702 | if (!*cp || *cp == '\n' || *cp == '#') |
484 | continue; | 703 | continue; |
485 | 704 | snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum); | |
486 | if (sshkey_read(found, &cp) != 0) { | 705 | if (check_authkey_line(ssh, pw, key, cp, loc, authoptsp) == 0) |
487 | /* no key? check if there are options for this key */ | ||
488 | int quoted = 0; | ||
489 | debug2("user_key_allowed: check options: '%s'", cp); | ||
490 | key_options = cp; | ||
491 | for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) { | ||
492 | if (*cp == '\\' && cp[1] == '"') | ||
493 | cp++; /* Skip both */ | ||
494 | else if (*cp == '"') | ||
495 | quoted = !quoted; | ||
496 | } | ||
497 | /* Skip remaining whitespace. */ | ||
498 | for (; *cp == ' ' || *cp == '\t'; cp++) | ||
499 | ; | ||
500 | if (sshkey_read(found, &cp) != 0) { | ||
501 | debug2("user_key_allowed: advance: '%s'", cp); | ||
502 | /* still no key? advance to next line*/ | ||
503 | continue; | ||
504 | } | ||
505 | } | ||
506 | if (sshkey_is_cert(key)) { | ||
507 | if (!sshkey_equal(found, key->cert->signature_key)) | ||
508 | continue; | ||
509 | if (auth_parse_options(pw, key_options, file, | ||
510 | linenum) != 1) | ||
511 | continue; | ||
512 | if (!key_is_cert_authority) | ||
513 | continue; | ||
514 | if ((fp = sshkey_fingerprint(found, | ||
515 | options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) | ||
516 | continue; | ||
517 | debug("matching CA found: file %s, line %lu, %s %s", | ||
518 | file, linenum, sshkey_type(found), fp); | ||
519 | /* | ||
520 | * If the user has specified a list of principals as | ||
521 | * a key option, then prefer that list to matching | ||
522 | * their username in the certificate principals list. | ||
523 | */ | ||
524 | if (authorized_principals != NULL && | ||
525 | !match_principals_option(authorized_principals, | ||
526 | key->cert)) { | ||
527 | reason = "Certificate does not contain an " | ||
528 | "authorized principal"; | ||
529 | fail_reason: | ||
530 | free(fp); | ||
531 | error("%s", reason); | ||
532 | auth_debug_add("%s", reason); | ||
533 | continue; | ||
534 | } | ||
535 | if (sshkey_cert_check_authority(key, 0, 0, | ||
536 | authorized_principals == NULL ? pw->pw_name : NULL, | ||
537 | &reason) != 0) | ||
538 | goto fail_reason; | ||
539 | if (auth_cert_options(key, pw, &reason) != 0) | ||
540 | goto fail_reason; | ||
541 | verbose("Accepted certificate ID \"%s\" (serial %llu) " | ||
542 | "signed by %s CA %s via %s", key->cert->key_id, | ||
543 | (unsigned long long)key->cert->serial, | ||
544 | sshkey_type(found), fp, file); | ||
545 | free(fp); | ||
546 | found_key = 1; | ||
547 | break; | ||
548 | } else if (sshkey_equal(found, key)) { | ||
549 | if (auth_parse_options(pw, key_options, file, | ||
550 | linenum) != 1) | ||
551 | continue; | ||
552 | if (key_is_cert_authority) | ||
553 | continue; | ||
554 | if ((fp = sshkey_fingerprint(found, | ||
555 | options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) | ||
556 | continue; | ||
557 | debug("matching key found: file %s, line %lu %s %s", | ||
558 | file, linenum, sshkey_type(found), fp); | ||
559 | free(fp); | ||
560 | found_key = 1; | 706 | found_key = 1; |
561 | continue; | ||
562 | } | ||
563 | } | 707 | } |
564 | done: | ||
565 | sshkey_free(found); | ||
566 | if (!found_key) | ||
567 | debug2("key not found"); | ||
568 | return found_key; | 708 | return found_key; |
569 | } | 709 | } |
570 | 710 | ||
571 | /* Authenticate a certificate key against TrustedUserCAKeys */ | 711 | /* Authenticate a certificate key against TrustedUserCAKeys */ |
572 | static int | 712 | static int |
573 | user_cert_trusted_ca(struct passwd *pw, struct sshkey *key) | 713 | user_cert_trusted_ca(struct ssh *ssh, struct passwd *pw, struct sshkey *key, |
714 | struct sshauthopt **authoptsp) | ||
574 | { | 715 | { |
575 | char *ca_fp, *principals_file = NULL; | 716 | char *ca_fp, *principals_file = NULL; |
576 | const char *reason; | 717 | const char *reason; |
718 | struct sshauthopt *principals_opts = NULL, *cert_opts = NULL; | ||
719 | struct sshauthopt *final_opts = NULL; | ||
577 | int r, ret = 0, found_principal = 0, use_authorized_principals; | 720 | int r, ret = 0, found_principal = 0, use_authorized_principals; |
578 | 721 | ||
722 | if (authoptsp != NULL) | ||
723 | *authoptsp = NULL; | ||
724 | |||
579 | if (!sshkey_is_cert(key) || options.trusted_user_ca_keys == NULL) | 725 | if (!sshkey_is_cert(key) || options.trusted_user_ca_keys == NULL) |
580 | return 0; | 726 | return 0; |
581 | 727 | ||
@@ -596,36 +742,69 @@ user_cert_trusted_ca(struct passwd *pw, struct sshkey *key) | |||
596 | * against the username. | 742 | * against the username. |
597 | */ | 743 | */ |
598 | if ((principals_file = authorized_principals_file(pw)) != NULL) { | 744 | if ((principals_file = authorized_principals_file(pw)) != NULL) { |
599 | if (match_principals_file(principals_file, pw, key->cert)) | 745 | if (match_principals_file(ssh, pw, principals_file, |
746 | key->cert, &principals_opts)) | ||
600 | found_principal = 1; | 747 | found_principal = 1; |
601 | } | 748 | } |
602 | /* Try querying command if specified */ | 749 | /* Try querying command if specified */ |
603 | if (!found_principal && match_principals_command(pw, key)) | 750 | if (!found_principal && match_principals_command(ssh, pw, key, |
751 | &principals_opts)) | ||
604 | found_principal = 1; | 752 | found_principal = 1; |
605 | /* If principals file or command is specified, then require a match */ | 753 | /* If principals file or command is specified, then require a match */ |
606 | use_authorized_principals = principals_file != NULL || | 754 | use_authorized_principals = principals_file != NULL || |
607 | options.authorized_principals_command != NULL; | 755 | options.authorized_principals_command != NULL; |
608 | if (!found_principal && use_authorized_principals) { | 756 | if (!found_principal && use_authorized_principals) { |
609 | reason = "Certificate does not contain an authorized principal"; | 757 | reason = "Certificate does not contain an authorized principal"; |
610 | fail_reason: | 758 | goto fail_reason; |
611 | error("%s", reason); | ||
612 | auth_debug_add("%s", reason); | ||
613 | goto out; | ||
614 | } | 759 | } |
760 | if (use_authorized_principals && principals_opts == NULL) | ||
761 | fatal("%s: internal error: missing principals_opts", __func__); | ||
615 | if (sshkey_cert_check_authority(key, 0, 1, | 762 | if (sshkey_cert_check_authority(key, 0, 1, |
616 | use_authorized_principals ? NULL : pw->pw_name, &reason) != 0) | 763 | use_authorized_principals ? NULL : pw->pw_name, &reason) != 0) |
617 | goto fail_reason; | 764 | goto fail_reason; |
618 | if (auth_cert_options(key, pw, &reason) != 0) | 765 | |
766 | /* Check authority from options in key and from principals file/cmd */ | ||
767 | if ((cert_opts = sshauthopt_from_cert(key)) == NULL) { | ||
768 | reason = "Invalid certificate options"; | ||
769 | goto fail_reason; | ||
770 | } | ||
771 | if (auth_authorise_keyopts(ssh, pw, cert_opts, 0, "cert") != 0) { | ||
772 | reason = "Refused by certificate options"; | ||
619 | goto fail_reason; | 773 | goto fail_reason; |
774 | } | ||
775 | if (principals_opts == NULL) { | ||
776 | final_opts = cert_opts; | ||
777 | cert_opts = NULL; | ||
778 | } else { | ||
779 | if (auth_authorise_keyopts(ssh, pw, principals_opts, 0, | ||
780 | "principals") != 0) { | ||
781 | reason = "Refused by certificate principals options"; | ||
782 | goto fail_reason; | ||
783 | } | ||
784 | if ((final_opts = sshauthopt_merge(principals_opts, | ||
785 | cert_opts, &reason)) == NULL) { | ||
786 | fail_reason: | ||
787 | error("%s", reason); | ||
788 | auth_debug_add("%s", reason); | ||
789 | goto out; | ||
790 | } | ||
791 | } | ||
620 | 792 | ||
793 | /* Success */ | ||
621 | verbose("Accepted certificate ID \"%s\" (serial %llu) signed by " | 794 | verbose("Accepted certificate ID \"%s\" (serial %llu) signed by " |
622 | "%s CA %s via %s", key->cert->key_id, | 795 | "%s CA %s via %s", key->cert->key_id, |
623 | (unsigned long long)key->cert->serial, | 796 | (unsigned long long)key->cert->serial, |
624 | sshkey_type(key->cert->signature_key), ca_fp, | 797 | sshkey_type(key->cert->signature_key), ca_fp, |
625 | options.trusted_user_ca_keys); | 798 | options.trusted_user_ca_keys); |
799 | if (authoptsp != NULL) { | ||
800 | *authoptsp = final_opts; | ||
801 | final_opts = NULL; | ||
802 | } | ||
626 | ret = 1; | 803 | ret = 1; |
627 | |||
628 | out: | 804 | out: |
805 | sshauthopt_free(principals_opts); | ||
806 | sshauthopt_free(cert_opts); | ||
807 | sshauthopt_free(final_opts); | ||
629 | free(principals_file); | 808 | free(principals_file); |
630 | free(ca_fp); | 809 | free(ca_fp); |
631 | return ret; | 810 | return ret; |
@@ -636,17 +815,22 @@ user_cert_trusted_ca(struct passwd *pw, struct sshkey *key) | |||
636 | * returns 1 if the key is allowed or 0 otherwise. | 815 | * returns 1 if the key is allowed or 0 otherwise. |
637 | */ | 816 | */ |
638 | static int | 817 | static int |
639 | user_key_allowed2(struct passwd *pw, struct sshkey *key, char *file) | 818 | user_key_allowed2(struct ssh *ssh, struct passwd *pw, struct sshkey *key, |
819 | char *file, struct sshauthopt **authoptsp) | ||
640 | { | 820 | { |
641 | FILE *f; | 821 | FILE *f; |
642 | int found_key = 0; | 822 | int found_key = 0; |
643 | 823 | ||
824 | if (authoptsp != NULL) | ||
825 | *authoptsp = NULL; | ||
826 | |||
644 | /* Temporarily use the user's uid. */ | 827 | /* Temporarily use the user's uid. */ |
645 | temporarily_use_uid(pw); | 828 | temporarily_use_uid(pw); |
646 | 829 | ||
647 | debug("trying public key file %s", file); | 830 | debug("trying public key file %s", file); |
648 | if ((f = auth_openkeyfile(file, pw, options.strict_modes)) != NULL) { | 831 | if ((f = auth_openkeyfile(file, pw, options.strict_modes)) != NULL) { |
649 | found_key = check_authkeys_file(f, file, key, pw); | 832 | found_key = check_authkeys_file(ssh, pw, f, file, |
833 | key, authoptsp); | ||
650 | fclose(f); | 834 | fclose(f); |
651 | } | 835 | } |
652 | 836 | ||
@@ -659,17 +843,20 @@ user_key_allowed2(struct passwd *pw, struct sshkey *key, char *file) | |||
659 | * returns 1 if the key is allowed or 0 otherwise. | 843 | * returns 1 if the key is allowed or 0 otherwise. |
660 | */ | 844 | */ |
661 | static int | 845 | static int |
662 | user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) | 846 | user_key_command_allowed2(struct ssh *ssh, struct passwd *user_pw, |
847 | struct sshkey *key, struct sshauthopt **authoptsp) | ||
663 | { | 848 | { |
849 | struct passwd *runas_pw = NULL; | ||
664 | FILE *f = NULL; | 850 | FILE *f = NULL; |
665 | int r, ok, found_key = 0; | 851 | int r, ok, found_key = 0; |
666 | struct passwd *pw; | ||
667 | int i, uid_swapped = 0, ac = 0; | 852 | int i, uid_swapped = 0, ac = 0; |
668 | pid_t pid; | 853 | pid_t pid; |
669 | char *username = NULL, *key_fp = NULL, *keytext = NULL; | 854 | char *username = NULL, *key_fp = NULL, *keytext = NULL; |
670 | char *tmp, *command = NULL, **av = NULL; | 855 | char *tmp, *command = NULL, **av = NULL; |
671 | void (*osigchld)(int); | 856 | void (*osigchld)(int); |
672 | 857 | ||
858 | if (authoptsp != NULL) | ||
859 | *authoptsp = NULL; | ||
673 | if (options.authorized_keys_command == NULL) | 860 | if (options.authorized_keys_command == NULL) |
674 | return 0; | 861 | return 0; |
675 | if (options.authorized_keys_command_user == NULL) { | 862 | if (options.authorized_keys_command_user == NULL) { |
@@ -686,8 +873,8 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) | |||
686 | /* Prepare and verify the user for the command */ | 873 | /* Prepare and verify the user for the command */ |
687 | username = percent_expand(options.authorized_keys_command_user, | 874 | username = percent_expand(options.authorized_keys_command_user, |
688 | "u", user_pw->pw_name, (char *)NULL); | 875 | "u", user_pw->pw_name, (char *)NULL); |
689 | pw = getpwnam(username); | 876 | runas_pw = getpwnam(username); |
690 | if (pw == NULL) { | 877 | if (runas_pw == NULL) { |
691 | error("AuthorizedKeysCommandUser \"%s\" not found: %s", | 878 | error("AuthorizedKeysCommandUser \"%s\" not found: %s", |
692 | username, strerror(errno)); | 879 | username, strerror(errno)); |
693 | goto out; | 880 | goto out; |
@@ -745,15 +932,16 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) | |||
745 | xasprintf(&command, "%s %s", av[0], av[1]); | 932 | xasprintf(&command, "%s %s", av[0], av[1]); |
746 | } | 933 | } |
747 | 934 | ||
748 | if ((pid = subprocess("AuthorizedKeysCommand", pw, command, | 935 | if ((pid = subprocess("AuthorizedKeysCommand", runas_pw, command, |
749 | ac, av, &f, | 936 | ac, av, &f, |
750 | SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD)) == 0) | 937 | SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD)) == 0) |
751 | goto out; | 938 | goto out; |
752 | 939 | ||
753 | uid_swapped = 1; | 940 | uid_swapped = 1; |
754 | temporarily_use_uid(pw); | 941 | temporarily_use_uid(runas_pw); |
755 | 942 | ||
756 | ok = check_authkeys_file(f, options.authorized_keys_command, key, pw); | 943 | ok = check_authkeys_file(ssh, user_pw, f, |
944 | options.authorized_keys_command, key, authoptsp); | ||
757 | 945 | ||
758 | fclose(f); | 946 | fclose(f); |
759 | f = NULL; | 947 | f = NULL; |
@@ -783,10 +971,14 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) | |||
783 | * Check whether key authenticates and authorises the user. | 971 | * Check whether key authenticates and authorises the user. |
784 | */ | 972 | */ |
785 | int | 973 | int |
786 | user_key_allowed(struct passwd *pw, struct sshkey *key, int auth_attempt) | 974 | user_key_allowed(struct ssh *ssh, struct passwd *pw, struct sshkey *key, |
975 | int auth_attempt, struct sshauthopt **authoptsp) | ||
787 | { | 976 | { |
788 | u_int success, i; | 977 | u_int success, i; |
789 | char *file; | 978 | char *file; |
979 | struct sshauthopt *opts = NULL; | ||
980 | if (authoptsp != NULL) | ||
981 | *authoptsp = NULL; | ||
790 | 982 | ||
791 | if (auth_key_is_revoked(key)) | 983 | if (auth_key_is_revoked(key)) |
792 | return 0; | 984 | return 0; |
@@ -794,25 +986,31 @@ user_key_allowed(struct passwd *pw, struct sshkey *key, int auth_attempt) | |||
794 | auth_key_is_revoked(key->cert->signature_key)) | 986 | auth_key_is_revoked(key->cert->signature_key)) |
795 | return 0; | 987 | return 0; |
796 | 988 | ||
797 | success = user_cert_trusted_ca(pw, key); | 989 | if ((success = user_cert_trusted_ca(ssh, pw, key, &opts)) != 0) |
798 | if (success) | 990 | goto out; |
799 | return success; | 991 | sshauthopt_free(opts); |
992 | opts = NULL; | ||
800 | 993 | ||
801 | success = user_key_command_allowed2(pw, key); | 994 | if ((success = user_key_command_allowed2(ssh, pw, key, &opts)) != 0) |
802 | if (success > 0) | 995 | goto out; |
803 | return success; | 996 | sshauthopt_free(opts); |
997 | opts = NULL; | ||
804 | 998 | ||
805 | for (i = 0; !success && i < options.num_authkeys_files; i++) { | 999 | for (i = 0; !success && i < options.num_authkeys_files; i++) { |
806 | |||
807 | if (strcasecmp(options.authorized_keys_files[i], "none") == 0) | 1000 | if (strcasecmp(options.authorized_keys_files[i], "none") == 0) |
808 | continue; | 1001 | continue; |
809 | file = expand_authorized_keys( | 1002 | file = expand_authorized_keys( |
810 | options.authorized_keys_files[i], pw); | 1003 | options.authorized_keys_files[i], pw); |
811 | 1004 | success = user_key_allowed2(ssh, pw, key, file, &opts); | |
812 | success = user_key_allowed2(pw, key, file); | ||
813 | free(file); | 1005 | free(file); |
814 | } | 1006 | } |
815 | 1007 | ||
1008 | out: | ||
1009 | if (success && authoptsp != NULL) { | ||
1010 | *authoptsp = opts; | ||
1011 | opts = NULL; | ||
1012 | } | ||
1013 | sshauthopt_free(opts); | ||
816 | return success; | 1014 | return success; |
817 | } | 1015 | } |
818 | 1016 | ||