xref: /openbsd/usr.bin/ssh/auth2-pubkeyfile.c (revision 15d7c2bc)
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
auth_authorise_keyopts(struct passwd * pw,struct sshauthopt * opts,int allow_cert_authority,const char * remote_ip,const char * remote_host,const char * loc)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
match_principals_option(const char * principal_list,struct sshkey_cert * cert)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
auth_check_principals_line(char * cp,const struct sshkey_cert * cert,const char * loc,struct sshauthopt ** authoptsp)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
auth_process_principals(FILE * f,const char * file,const struct sshkey_cert * cert,struct sshauthopt ** authoptsp)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
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)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
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)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 *
auth_openfile(const char * file,struct passwd * pw,int strict_modes,int log_missing,char * file_type)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 *
auth_openkeyfile(const char * file,struct passwd * pw,int strict_modes)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 *
auth_openprincipals(const char * file,struct passwd * pw,int strict_modes)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