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