1/* $NetBSD: auth2-pubkeyfile.c,v 1.3 2023/07/26 17:58:15 christos Exp $ */ 2/* $OpenBSD: auth2-pubkeyfile.c,v 1.4 2023/03/05 05:34:09 dtucker Exp $ */ 3/* 4 * Copyright (c) 2000 Markus Friedl. All rights reserved. 5 * Copyright (c) 2010 Damien Miller. 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 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28#include "includes.h" 29__RCSID("$NetBSD: auth2-pubkeyfile.c,v 1.3 2023/07/26 17:58:15 christos Exp $"); 30 31#include <sys/types.h> 32#include <sys/stat.h> 33 34#include <stdlib.h> 35#include <errno.h> 36#include <fcntl.h> 37#include <pwd.h> 38#include <stdio.h> 39#include <stdarg.h> 40#include <string.h> 41#include <time.h> 42#include <unistd.h> 43 44#include "ssh.h" 45#include "log.h" 46#include "misc.h" 47#include "sshkey.h" 48#include "digest.h" 49#include "hostfile.h" 50#include "auth.h" 51#include "auth-options.h" 52#include "authfile.h" 53#include "match.h" 54#include "ssherr.h" 55 56#ifdef WITH_LDAP_PUBKEY 57#include "servconf.h" 58#include "uidswap.h" 59#include "ldapauth.h" 60 61extern ServerOptions options; 62#endif 63 64int 65auth_authorise_keyopts(struct passwd *pw, struct sshauthopt *opts, 66 int allow_cert_authority, const char *remote_ip, const char *remote_host, 67 const char *loc) 68{ 69 time_t now = time(NULL); 70 char buf[64]; 71 72 /* 73 * Check keys/principals file expiry time. 74 * NB. validity interval in certificate is handled elsewhere. 75 */ 76 if (opts->valid_before && now > 0 && 77 opts->valid_before < (uint64_t)now) { 78 format_absolute_time(opts->valid_before, buf, sizeof(buf)); 79 debug("%s: entry expired at %s", loc, buf); 80 auth_debug_add("%s: entry expired at %s", loc, buf); 81 return -1; 82 } 83 /* Consistency checks */ 84 if (opts->cert_principals != NULL && !opts->cert_authority) { 85 debug("%s: principals on non-CA key", loc); 86 auth_debug_add("%s: principals on non-CA key", loc); 87 /* deny access */ 88 return -1; 89 } 90 /* cert-authority flag isn't valid in authorized_principals files */ 91 if (!allow_cert_authority && opts->cert_authority) { 92 debug("%s: cert-authority flag invalid here", loc); 93 auth_debug_add("%s: cert-authority flag invalid here", loc); 94 /* deny access */ 95 return -1; 96 } 97 98 /* Perform from= checks */ 99 if (opts->required_from_host_keys != NULL) { 100 switch (match_host_and_ip(remote_host, remote_ip, 101 opts->required_from_host_keys )) { 102 case 1: 103 /* Host name matches. */ 104 break; 105 case -1: 106 default: 107 debug("%s: invalid from criteria", loc); 108 auth_debug_add("%s: invalid from criteria", loc); 109 /* FALLTHROUGH */ 110 case 0: 111 logit("%s: Authentication tried for %.100s with " 112 "correct key but not from a permitted " 113 "host (host=%.200s, ip=%.200s, required=%.200s).", 114 loc, pw->pw_name, remote_host, remote_ip, 115 opts->required_from_host_keys); 116 auth_debug_add("%s: Your host '%.200s' is not " 117 "permitted to use this key for login.", 118 loc, remote_host); 119 /* deny access */ 120 return -1; 121 } 122 } 123 /* Check source-address restriction from certificate */ 124 if (opts->required_from_host_cert != NULL) { 125 switch (addr_match_cidr_list(remote_ip, 126 opts->required_from_host_cert)) { 127 case 1: 128 /* accepted */ 129 break; 130 case -1: 131 default: 132 /* invalid */ 133 error("%s: Certificate source-address invalid", loc); 134 /* FALLTHROUGH */ 135 case 0: 136 logit("%s: Authentication tried for %.100s with valid " 137 "certificate but not from a permitted source " 138 "address (%.200s).", loc, pw->pw_name, remote_ip); 139 auth_debug_add("%s: Your address '%.200s' is not " 140 "permitted to use this certificate for login.", 141 loc, remote_ip); 142 return -1; 143 } 144 } 145 /* 146 * 147 * XXX this is spammy. We should report remotely only for keys 148 * that are successful in actual auth attempts, and not PK_OK 149 * tests. 150 */ 151 auth_log_authopts(loc, opts, 1); 152 153 return 0; 154} 155 156static int 157match_principals_option(const char *principal_list, struct sshkey_cert *cert) 158{ 159 char *result; 160 u_int i; 161 162 /* XXX percent_expand() sequences for authorized_principals? */ 163 164 for (i = 0; i < cert->nprincipals; i++) { 165 if ((result = match_list(cert->principals[i], 166 principal_list, NULL)) != NULL) { 167 debug3("matched principal from key options \"%.100s\"", 168 result); 169 free(result); 170 return 1; 171 } 172 } 173 return 0; 174} 175 176/* 177 * Process a single authorized_principals format line. Returns 0 and sets 178 * authoptsp is principal is authorised, -1 otherwise. "loc" is used as a 179 * log preamble for file/line information. 180 */ 181int 182auth_check_principals_line(char *cp, const struct sshkey_cert *cert, 183 const char *loc, struct sshauthopt **authoptsp) 184{ 185 u_int i, found = 0; 186 char *ep, *line_opts; 187 const char *reason = NULL; 188 struct sshauthopt *opts = NULL; 189 190 if (authoptsp != NULL) 191 *authoptsp = NULL; 192 193 /* Trim trailing whitespace. */ 194 ep = cp + strlen(cp) - 1; 195 while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t')) 196 *ep-- = '\0'; 197 198 /* 199 * If the line has internal whitespace then assume it has 200 * key options. 201 */ 202 line_opts = NULL; 203 if ((ep = strrchr(cp, ' ')) != NULL || 204 (ep = strrchr(cp, '\t')) != NULL) { 205 for (; *ep == ' ' || *ep == '\t'; ep++) 206 ; 207 line_opts = cp; 208 cp = ep; 209 } 210 if ((opts = sshauthopt_parse(line_opts, &reason)) == NULL) { 211 debug("%s: bad principals options: %s", loc, reason); 212 auth_debug_add("%s: bad principals options: %s", loc, reason); 213 return -1; 214 } 215 /* Check principals in cert against those on line */ 216 for (i = 0; i < cert->nprincipals; i++) { 217 if (strcmp(cp, cert->principals[i]) != 0) 218 continue; 219 debug3("%s: matched principal \"%.100s\"", 220 loc, cert->principals[i]); 221 found = 1; 222 } 223 if (found && authoptsp != NULL) { 224 *authoptsp = opts; 225 opts = NULL; 226 } 227 sshauthopt_free(opts); 228 return found ? 0 : -1; 229} 230 231int 232auth_process_principals(FILE *f, const char *file, 233 const struct sshkey_cert *cert, struct sshauthopt **authoptsp) 234{ 235 char loc[256], *line = NULL, *cp, *ep; 236 size_t linesize = 0; 237 u_long linenum = 0, nonblank = 0; 238 u_int found_principal = 0; 239 240 if (authoptsp != NULL) 241 *authoptsp = NULL; 242 243 while (getline(&line, &linesize, f) != -1) { 244 linenum++; 245 /* Always consume entire input */ 246 if (found_principal) 247 continue; 248 249 /* Skip leading whitespace. */ 250 for (cp = line; *cp == ' ' || *cp == '\t'; cp++) 251 ; 252 /* Skip blank and comment lines. */ 253 if ((ep = strchr(cp, '#')) != NULL) 254 *ep = '\0'; 255 if (!*cp || *cp == '\n') 256 continue; 257 258 nonblank++; 259 snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum); 260 if (auth_check_principals_line(cp, cert, loc, authoptsp) == 0) 261 found_principal = 1; 262 } 263 debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum); 264 free(line); 265 return found_principal; 266} 267 268/* 269 * Check a single line of an authorized_keys-format file. Returns 0 if key 270 * matches, -1 otherwise. Will return key/cert options via *authoptsp 271 * on success. "loc" is used as file/line location in log messages. 272 */ 273int 274auth_check_authkey_line(struct passwd *pw, struct sshkey *key, 275 char *cp, const char *remote_ip, const char *remote_host, const char *loc, 276 struct sshauthopt **authoptsp) 277{ 278 int want_keytype = sshkey_is_cert(key) ? KEY_UNSPEC : key->type; 279 struct sshkey *found = NULL; 280 struct sshauthopt *keyopts = NULL, *certopts = NULL, *finalopts = NULL; 281 char *key_options = NULL, *fp = NULL; 282 const char *reason = NULL; 283 int ret = -1; 284 285 if (authoptsp != NULL) 286 *authoptsp = NULL; 287 288 if ((found = sshkey_new(want_keytype)) == NULL) { 289 debug3_f("keytype %d failed", want_keytype); 290 goto out; 291 } 292 293 /* XXX djm: peek at key type in line and skip if unwanted */ 294 295 if (sshkey_read(found, &cp) != 0) { 296 /* no key? check for options */ 297 debug2("%s: check options: '%s'", loc, cp); 298 key_options = cp; 299 if (sshkey_advance_past_options(&cp) != 0) { 300 reason = "invalid key option string"; 301 goto fail_reason; 302 } 303 skip_space(&cp); 304 if (sshkey_read(found, &cp) != 0) { 305 /* still no key? advance to next line*/ 306 debug2("%s: advance: '%s'", loc, cp); 307 goto out; 308 } 309 } 310 /* Parse key options now; we need to know if this is a CA key */ 311 if ((keyopts = sshauthopt_parse(key_options, &reason)) == NULL) { 312 debug("%s: bad key options: %s", loc, reason); 313 auth_debug_add("%s: bad key options: %s", loc, reason); 314 goto out; 315 } 316 /* Ignore keys that don't match or incorrectly marked as CAs */ 317 if (sshkey_is_cert(key)) { 318 /* Certificate; check signature key against CA */ 319 if (!sshkey_equal(found, key->cert->signature_key) || 320 !keyopts->cert_authority) 321 goto out; 322 } else { 323 /* Plain key: check it against key found in file */ 324 if (!sshkey_equal(found, key) || keyopts->cert_authority) 325 goto out; 326 } 327 328 /* We have a candidate key, perform authorisation checks */ 329 if ((fp = sshkey_fingerprint(found, 330 SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL) 331 fatal_f("fingerprint failed"); 332 333 debug("%s: matching %s found: %s %s", loc, 334 sshkey_is_cert(key) ? "CA" : "key", sshkey_type(found), fp); 335 336 if (auth_authorise_keyopts(pw, keyopts, 337 sshkey_is_cert(key), remote_ip, remote_host, loc) != 0) { 338 reason = "Refused by key options"; 339 goto fail_reason; 340 } 341 /* That's all we need for plain keys. */ 342 if (!sshkey_is_cert(key)) { 343 verbose("Accepted key %s %s found at %s", 344 sshkey_type(found), fp, loc); 345 finalopts = keyopts; 346 keyopts = NULL; 347 goto success; 348 } 349 350 /* 351 * Additional authorisation for certificates. 352 */ 353 354 /* Parse and check options present in certificate */ 355 if ((certopts = sshauthopt_from_cert(key)) == NULL) { 356 reason = "Invalid certificate options"; 357 goto fail_reason; 358 } 359 if (auth_authorise_keyopts(pw, certopts, 0, 360 remote_ip, remote_host, loc) != 0) { 361 reason = "Refused by certificate options"; 362 goto fail_reason; 363 } 364 if ((finalopts = sshauthopt_merge(keyopts, certopts, &reason)) == NULL) 365 goto fail_reason; 366 367 /* 368 * If the user has specified a list of principals as 369 * a key option, then prefer that list to matching 370 * their username in the certificate principals list. 371 */ 372 if (keyopts->cert_principals != NULL && 373 !match_principals_option(keyopts->cert_principals, key->cert)) { 374 reason = "Certificate does not contain an authorized principal"; 375 goto fail_reason; 376 } 377 if (sshkey_cert_check_authority_now(key, 0, 0, 0, 378 keyopts->cert_principals == NULL ? pw->pw_name : NULL, 379 &reason) != 0) 380 goto fail_reason; 381 382 verbose("Accepted certificate ID \"%s\" (serial %llu) " 383 "signed by CA %s %s found at %s", 384 key->cert->key_id, 385 (unsigned long long)key->cert->serial, 386 sshkey_type(found), fp, loc); 387 388 success: 389 if (finalopts == NULL) 390 fatal_f("internal error: missing options"); 391 if (authoptsp != NULL) { 392 *authoptsp = finalopts; 393 finalopts = NULL; 394 } 395 /* success */ 396 ret = 0; 397 goto out; 398 399 fail_reason: 400 error("%s", reason); 401 auth_debug_add("%s", reason); 402 out: 403 free(fp); 404 sshauthopt_free(keyopts); 405 sshauthopt_free(certopts); 406 sshauthopt_free(finalopts); 407 sshkey_free(found); 408 return ret; 409} 410 411/* 412 * Checks whether key is allowed in authorized_keys-format file, 413 * returns 1 if the key is allowed or 0 otherwise. 414 */ 415int 416auth_check_authkeys_file(struct passwd *pw, FILE *f, char *file, 417 struct sshkey *key, const char *remote_ip, 418 const char *remote_host, struct sshauthopt **authoptsp) 419{ 420 char *cp, *line = NULL, loc[256]; 421 size_t linesize = 0; 422 int found_key = 0; 423 u_long linenum = 0, nonblank = 0; 424#ifdef WITH_LDAP_PUBKEY 425 struct sshauthopt *opts = NULL; 426 struct sshkey *found = NULL; 427 ldap_key_t * k; 428 unsigned int i = 0; 429 const char *reason; 430 431 found_key = 0; 432 /* allocate a new key type */ 433 found = sshkey_new(key->type); 434 435 /* first check if the options is enabled, then try.. */ 436 if (options.lpk.on) { 437 debug("[LDAP] trying LDAP first uid=%s",pw->pw_name); 438 if (ldap_ismember(&options.lpk, pw->pw_name) > 0) { 439 if ((k = ldap_getuserkey(&options.lpk, pw->pw_name)) != NULL) { 440 /* Skip leading whitespace, empty and comment lines. */ 441 for (i = 0 ; i < k->num ; i++) { 442 /* dont forget if multiple keys to reset options */ 443 char *xoptions = NULL; 444 445 for (cp = (char *)k->keys[i]->bv_val; *cp == ' ' || *cp == '\t'; cp++) 446 ; 447 if (!*cp || *cp == '\n' || *cp == '#') 448 continue; 449 450 if (sshkey_read(found, &cp) != 0) { 451 /* no key? check if there are options for this key */ 452 int quoted = 0; 453 debug2("[LDAP] user_key_allowed: check options: '%s'", cp); 454 xoptions = cp; 455 for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) { 456 if (*cp == '\\' && cp[1] == '"') 457 cp++; /* Skip both */ 458 else if (*cp == '"') 459 quoted = !quoted; 460 } 461 /* Skip remaining whitespace. */ 462 for (; *cp == ' ' || *cp == '\t'; cp++) 463 ; 464 if (sshkey_read(found, &cp) != 0) { 465 debug2("[LDAP] user_key_allowed: advance: '%s'", cp); 466 /* still no key? advance to next line*/ 467 continue; 468 } 469 } 470 if ((opts = sshauthopt_parse(xoptions, &reason)) == NULL) { 471 debug("[LDAP] %s: bad principals options: %s", xoptions, reason); 472 auth_debug_add("[LDAP] %s: bad principals options: %s", xoptions, reason); 473 continue; 474 } 475 476 477 if (sshkey_equal(found, key)) { 478 found_key = 1; 479 debug("[LDAP] matching key found"); 480 char *fp = sshkey_fingerprint(found, SSH_FP_HASH_DEFAULT, SSH_FP_HEX); 481 verbose("[LDAP] Found matching %s key: %s", sshkey_type(found), fp); 482 483 /* restoring memory */ 484 ldap_keys_free(k); 485 free(fp); 486 restore_uid(); 487 sshkey_free(found); 488 return found_key; 489 break; 490 } 491 }/* end of LDAP for() */ 492 } else { 493 logit("[LDAP] no keys found for '%s'!", pw->pw_name); 494 } 495 } else { 496 logit("[LDAP] '%s' is not in '%s'", pw->pw_name, options.lpk.sgroup); 497 } 498 } 499#endif 500 501 if (authoptsp != NULL) 502 *authoptsp = NULL; 503 504 while (getline(&line, &linesize, f) != -1) { 505 linenum++; 506 /* Always consume entire file */ 507 if (found_key) 508 continue; 509 510 /* Skip leading whitespace, empty and comment lines. */ 511 cp = line; 512 skip_space(&cp); 513 if (!*cp || *cp == '\n' || *cp == '#') 514 continue; 515 516 nonblank++; 517 snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum); 518 if (auth_check_authkey_line(pw, key, cp, 519 remote_ip, remote_host, loc, authoptsp) == 0) 520 found_key = 1; 521 } 522 free(line); 523 debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum); 524 return found_key; 525} 526 527static FILE * 528auth_openfile(const char *file, struct passwd *pw, int strict_modes, 529 int log_missing, const char *file_type) 530{ 531 char line[1024]; 532 struct stat st; 533 int fd; 534 FILE *f; 535 536 if ((fd = open(file, O_RDONLY|O_NONBLOCK)) == -1) { 537 if (errno != ENOENT) { 538 logit("Could not open user '%s' %s '%s': %s", 539 pw->pw_name, file_type, file, strerror(errno)); 540 } else if (log_missing) { 541 debug("Could not open user '%s' %s '%s': %s", 542 pw->pw_name, file_type, file, strerror(errno)); 543 } 544 return NULL; 545 } 546 547 if (fstat(fd, &st) == -1) { 548 close(fd); 549 return NULL; 550 } 551 if (!S_ISREG(st.st_mode)) { 552 logit("User '%s' %s '%s' is not a regular file", 553 pw->pw_name, file_type, file); 554 close(fd); 555 return NULL; 556 } 557 unset_nonblock(fd); 558 if ((f = fdopen(fd, "r")) == NULL) { 559 close(fd); 560 return NULL; 561 } 562 if (strict_modes && 563 safe_path_fd(fileno(f), file, pw, line, sizeof(line)) != 0) { 564 fclose(f); 565 logit("Authentication refused: %s", line); 566 auth_debug_add("Ignored %s: %s", file_type, line); 567 return NULL; 568 } 569 570 return f; 571} 572 573 574FILE * 575auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes) 576{ 577 return auth_openfile(file, pw, strict_modes, 1, "authorized keys"); 578} 579 580FILE * 581auth_openprincipals(const char *file, struct passwd *pw, int strict_modes) 582{ 583 return auth_openfile(file, pw, strict_modes, 0, 584 "authorized principals"); 585} 586 587