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
54int
55auth_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
146static int
147match_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 */
171int
172auth_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
221int
222auth_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 */
263int
264auth_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 */
405int
406auth_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
441static FILE *
442auth_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
488FILE *
489auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes)
490{
491	return auth_openfile(file, pw, strict_modes, 1, "authorized keys");
492}
493
494FILE *
495auth_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