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