xref: /freebsd/crypto/openssh/auth2-pubkeyfile.c (revision 4d3fc8b0570b29fb0d6ee9525f104d52176ff0d4)
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
auth_authorise_keyopts(struct passwd * pw,struct sshauthopt * opts,int allow_cert_authority,const char * remote_ip,const char * remote_host,const char * loc)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
match_principals_option(const char * principal_list,struct sshkey_cert * cert)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
auth_check_principals_line(char * cp,const struct sshkey_cert * cert,const char * loc,struct sshauthopt ** authoptsp)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
auth_process_principals(FILE * f,const char * file,const struct sshkey_cert * cert,struct sshauthopt ** authoptsp)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
auth_check_authkey_line(struct passwd * pw,struct sshkey * key,char * cp,const char * remote_ip,const char * remote_host,const char * loc,struct sshauthopt ** authoptsp)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
auth_check_authkeys_file(struct passwd * pw,FILE * f,char * file,struct sshkey * key,const char * remote_ip,const char * remote_host,struct sshauthopt ** authoptsp)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 *
auth_openfile(const char * file,struct passwd * pw,int strict_modes,int log_missing,char * file_type)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 *
auth_openkeyfile(const char * file,struct passwd * pw,int strict_modes)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 *
auth_openprincipals(const char * file,struct passwd * pw,int strict_modes)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