[openssh-commits] [openssh] 02/02: upstream: split the low-level file handling functions out from

git+noreply at mindrot.org git+noreply at mindrot.org
Fri May 27 16:38:25 AEST 2022


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit c83d8c4d6f3ccceef84d46de107f6b71cda06359
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Fri May 27 05:02:46 2022 +0000

    upstream: split the low-level file handling functions out from
    
    auth2-pubkey.c
    
    Put them in a new auth2-pubkeyfile.c to make it easier to refer to them
    (e.g. in unit/fuzz tests) without having to refer to everything else
    pubkey auth brings in.
    
    ok dtucker@
    
    OpenBSD-Commit-ID: 3fdca2c61ad97dc1b8d4a7346816f83dc4ce2217
---
 Makefile.in        |   2 +-
 auth.c             |  94 +-----------
 auth.h             |  18 ++-
 auth2-pubkey.c     | 311 +------------------------------------
 auth2-pubkeyfile.c | 442 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 465 insertions(+), 402 deletions(-)

diff --git a/Makefile.in b/Makefile.in
index 7250d3f3..3c285682 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -123,7 +123,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o \
 	auth.o auth2.o auth-options.o session.o \
 	auth2-chall.o groupaccess.o \
 	auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
-	auth2-none.o auth2-passwd.o auth2-pubkey.o \
+	auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \
 	monitor.o monitor_wrap.o auth-krb5.o \
 	auth2-gss.o gss-serv.o gss-serv-krb5.o \
 	loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
diff --git a/auth.c b/auth.c
index 57ade8db..9ad9034a 100644
--- a/auth.c
+++ b/auth.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth.c,v 1.156 2022/05/27 05:01:25 djm Exp $ */
+/* $OpenBSD: auth.c,v 1.157 2022/05/27 05:02:46 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  *
@@ -912,95 +912,3 @@ auth_restrict_session(struct ssh *ssh)
 		fatal_f("failed to restrict session");
 	sshauthopt_free(restricted);
 }
-
-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)
-{
-	time_t now = time(NULL);
-	char buf[64];
-
-	/*
-	 * Check keys/principals file expiry time.
-	 * NB. validity interval in certificate is handled elsewhere.
-	 */
-	if (opts->valid_before && now > 0 &&
-	    opts->valid_before < (uint64_t)now) {
-		format_absolute_time(opts->valid_before, buf, sizeof(buf));
-		debug("%s: entry expired at %s", loc, buf);
-		auth_debug_add("%s: entry expired at %s", loc, buf);
-		return -1;
-	}
-	/* Consistency checks */
-	if (opts->cert_principals != NULL && !opts->cert_authority) {
-		debug("%s: principals on non-CA key", loc);
-		auth_debug_add("%s: principals on non-CA key", loc);
-		/* deny access */
-		return -1;
-	}
-	/* cert-authority flag isn't valid in authorized_principals files */
-	if (!allow_cert_authority && opts->cert_authority) {
-		debug("%s: cert-authority flag invalid here", loc);
-		auth_debug_add("%s: cert-authority flag invalid here", loc);
-		/* deny access */
-		return -1;
-	}
-
-	/* Perform from= checks */
-	if (opts->required_from_host_keys != NULL) {
-		switch (match_host_and_ip(remote_host, remote_ip,
-		    opts->required_from_host_keys )) {
-		case 1:
-			/* Host name matches. */
-			break;
-		case -1:
-		default:
-			debug("%s: invalid from criteria", loc);
-			auth_debug_add("%s: invalid from criteria", loc);
-			/* FALLTHROUGH */
-		case 0:
-			logit("%s: Authentication tried for %.100s with "
-			    "correct key but not from a permitted "
-			    "host (host=%.200s, ip=%.200s, required=%.200s).",
-			    loc, pw->pw_name, remote_host, remote_ip,
-			    opts->required_from_host_keys);
-			auth_debug_add("%s: Your host '%.200s' is not "
-			    "permitted to use this key for login.",
-			    loc, remote_host);
-			/* deny access */
-			return -1;
-		}
-	}
-	/* Check source-address restriction from certificate */
-	if (opts->required_from_host_cert != NULL) {
-		switch (addr_match_cidr_list(remote_ip,
-		    opts->required_from_host_cert)) {
-		case 1:
-			/* accepted */
-			break;
-		case -1:
-		default:
-			/* invalid */
-			error("%s: Certificate source-address invalid", loc);
-			/* FALLTHROUGH */
-		case 0:
-			logit("%s: Authentication tried for %.100s with valid "
-			    "certificate but not from a permitted source "
-			    "address (%.200s).", loc, pw->pw_name, remote_ip);
-			auth_debug_add("%s: Your address '%.200s' is not "
-			    "permitted to use this certificate for login.",
-			    loc, remote_ip);
-			return -1;
-		}
-	}
-	/*
-	 *
-	 * XXX this is spammy. We should report remotely only for keys
-	 *     that are successful in actual auth attempts, and not PK_OK
-	 *     tests.
-	 */
-	auth_log_authopts(loc, opts, 1);
-
-	return 0;
-}
diff --git a/auth.h b/auth.h
index a52ba7c2..b8eec4a6 100644
--- a/auth.h
+++ b/auth.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth.h,v 1.103 2022/05/27 05:01:25 djm Exp $ */
+/* $OpenBSD: auth.h,v 1.104 2022/05/27 05:02:46 djm Exp $ */
 
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
@@ -29,6 +29,7 @@
 #define AUTH_H
 
 #include <signal.h>
+#include <stdio.h>
 
 #ifdef HAVE_LOGIN_CAP
 #include <login_cap.h>
@@ -44,6 +45,7 @@ struct passwd;
 struct ssh;
 struct sshbuf;
 struct sshkey;
+struct sshkey_cert;
 struct sshauthopt;
 
 typedef struct Authctxt Authctxt;
@@ -214,8 +216,6 @@ int	 sshd_hostkey_sign(struct ssh *, struct sshkey *, struct sshkey *,
 const struct sshauthopt *auth_options(struct ssh *);
 int	 auth_activate_options(struct ssh *, struct sshauthopt *);
 void	 auth_restrict_session(struct ssh *);
-int	 auth_authorise_keyopts(struct passwd *pw, struct sshauthopt *, int,
-    const char *, const char *, const char *);
 void	 auth_log_authopts(const char *, const struct sshauthopt *, int);
 
 /* debug messages during authentication */
@@ -226,6 +226,18 @@ void	 auth_debug_reset(void);
 
 struct passwd *fakepw(void);
 
+/* auth2-pubkeyfile.c */
+int	 auth_authorise_keyopts(struct passwd *, struct sshauthopt *, int,
+    const char *, const char *, const char *);
+int	 auth_check_principals_line(char *, const struct sshkey_cert *,
+    const char *, struct sshauthopt **);
+int	 auth_process_principals(FILE *, const char *,
+    const struct sshkey_cert *, struct sshauthopt **);
+int	 auth_check_authkey_line(struct passwd *, struct sshkey *,
+    char *, const char *, const char *, const char *, struct sshauthopt **);
+int	 auth_check_authkeys_file(struct passwd *, FILE *, char *,
+    struct sshkey *, const char *, const char *, struct sshauthopt **);
+
 int	 sys_auth_passwd(struct ssh *, const char *);
 
 #if defined(KRB5) && !defined(HEIMDAL)
diff --git a/auth2-pubkey.c b/auth2-pubkey.c
index 2f58a138..952af119 100644
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -1,6 +1,7 @@
-/* $OpenBSD: auth2-pubkey.c,v 1.114 2022/05/27 05:01:25 djm Exp $ */
+/* $OpenBSD: auth2-pubkey.c,v 1.115 2022/05/27 05:02:46 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2010 Damien Miller.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -26,11 +27,9 @@
 #include "includes.h"
 
 #include <sys/types.h>
-#include <sys/stat.h>
 
 #include <stdlib.h>
 #include <errno.h>
-#include <fcntl.h>
 #ifdef HAVE_PATHS_H
 # include <paths.h>
 #endif
@@ -67,7 +66,6 @@
 #include "authfile.h"
 #include "match.h"
 #include "ssherr.h"
-#include "kex.h"
 #include "channels.h" /* XXX for session.h */
 #include "session.h" /* XXX for child_set_env(); refactor? */
 #include "sk-api.h"
@@ -321,120 +319,6 @@ done:
 	return authenticated;
 }
 
-static int
-match_principals_option(const char *principal_list, struct sshkey_cert *cert)
-{
-	char *result;
-	u_int i;
-
-	/* XXX percent_expand() sequences for authorized_principals? */
-
-	for (i = 0; i < cert->nprincipals; i++) {
-		if ((result = match_list(cert->principals[i],
-		    principal_list, NULL)) != NULL) {
-			debug3("matched principal from key options \"%.100s\"",
-			    result);
-			free(result);
-			return 1;
-		}
-	}
-	return 0;
-}
-
-/*
- * Process a single authorized_principals format line. Returns 0 and sets
- * authoptsp is principal is authorised, -1 otherwise. "loc" is used as a
- * log preamble for file/line information.
- */
-static int
-check_principals_line(char *cp, const struct sshkey_cert *cert,
-    const char *loc, struct sshauthopt **authoptsp)
-{
-	u_int i, found = 0;
-	char *ep, *line_opts;
-	const char *reason = NULL;
-	struct sshauthopt *opts = NULL;
-
-	if (authoptsp != NULL)
-		*authoptsp = NULL;
-
-	/* Trim trailing whitespace. */
-	ep = cp + strlen(cp) - 1;
-	while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t'))
-		*ep-- = '\0';
-
-	/*
-	 * If the line has internal whitespace then assume it has
-	 * key options.
-	 */
-	line_opts = NULL;
-	if ((ep = strrchr(cp, ' ')) != NULL ||
-	    (ep = strrchr(cp, '\t')) != NULL) {
-		for (; *ep == ' ' || *ep == '\t'; ep++)
-			;
-		line_opts = cp;
-		cp = ep;
-	}
-	if ((opts = sshauthopt_parse(line_opts, &reason)) == NULL) {
-		debug("%s: bad principals options: %s", loc, reason);
-		auth_debug_add("%s: bad principals options: %s", loc, reason);
-		return -1;
-	}
-	/* Check principals in cert against those on line */
-	for (i = 0; i < cert->nprincipals; i++) {
-		if (strcmp(cp, cert->principals[i]) != 0)
-			continue;
-		debug3("%s: matched principal \"%.100s\"",
-		    loc, cert->principals[i]);
-		found = 1;
-	}
-	if (found && authoptsp != NULL) {
-		*authoptsp = opts;
-		opts = NULL;
-	}
-	sshauthopt_free(opts);
-	return found ? 0 : -1;
-}
-
-static int
-process_principals(FILE *f, const char *file,
-    const struct sshkey_cert *cert, struct sshauthopt **authoptsp)
-{
-	char loc[256], *line = NULL, *cp, *ep;
-	size_t linesize = 0;
-	u_long linenum = 0, nonblank = 0;
-	u_int found_principal = 0;
-
-	if (authoptsp != NULL)
-		*authoptsp = NULL;
-
-	while (getline(&line, &linesize, f) != -1) {
-		linenum++;
-		/* Always consume entire input */
-		if (found_principal)
-			continue;
-
-		/* Skip leading whitespace. */
-		for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
-			;
-		/* Skip blank and comment lines. */
-		if ((ep = strchr(cp, '#')) != NULL)
-			*ep = '\0';
-		if (!*cp || *cp == '\n')
-			continue;
-
-		nonblank++;
-		snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum);
-		if (check_principals_line(cp, cert, loc, authoptsp) == 0)
-			found_principal = 1;
-	}
-	debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum);
-	free(line);
-	return found_principal;
-}
-
-/* XXX remove pw args here and elsewhere once ssh->authctxt is guaranteed */
-
 static int
 match_principals_file(struct passwd *pw, char *file,
     struct sshkey_cert *cert, struct sshauthopt **authoptsp)
@@ -451,7 +335,7 @@ match_principals_file(struct passwd *pw, char *file,
 		restore_uid();
 		return 0;
 	}
-	success = process_principals(f, file, cert, authoptsp);
+	success = auth_process_principals(f, file, cert, authoptsp);
 	fclose(f);
 	restore_uid();
 	return success;
@@ -567,7 +451,7 @@ match_principals_command(struct passwd *user_pw,
 	uid_swapped = 1;
 	temporarily_use_uid(runas_pw);
 
-	ok = process_principals(f, "(command)", cert, authoptsp);
+	ok = auth_process_principals(f, "(command)", cert, authoptsp);
 
 	fclose(f);
 	f = NULL;
@@ -595,189 +479,6 @@ match_principals_command(struct passwd *user_pw,
 	return found_principal;
 }
 
-/*
- * Check a single line of an authorized_keys-format file. Returns 0 if key
- * matches, -1 otherwise. Will return key/cert options via *authoptsp
- * on success. "loc" is used as file/line location in log messages.
- */
-static int
-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)
-{
-	int want_keytype = sshkey_is_cert(key) ? KEY_UNSPEC : key->type;
-	struct sshkey *found = NULL;
-	struct sshauthopt *keyopts = NULL, *certopts = NULL, *finalopts = NULL;
-	char *key_options = NULL, *fp = NULL;
-	const char *reason = NULL;
-	int ret = -1;
-
-	if (authoptsp != NULL)
-		*authoptsp = NULL;
-
-	if ((found = sshkey_new(want_keytype)) == NULL) {
-		debug3_f("keytype %d failed", want_keytype);
-		goto out;
-	}
-
-	/* XXX djm: peek at key type in line and skip if unwanted */
-
-	if (sshkey_read(found, &cp) != 0) {
-		/* no key?  check for options */
-		debug2("%s: check options: '%s'", loc, cp);
-		key_options = cp;
-		if (sshkey_advance_past_options(&cp) != 0) {
-			reason = "invalid key option string";
-			goto fail_reason;
-		}
-		skip_space(&cp);
-		if (sshkey_read(found, &cp) != 0) {
-			/* still no key?  advance to next line*/
-			debug2("%s: advance: '%s'", loc, cp);
-			goto out;
-		}
-	}
-	/* Parse key options now; we need to know if this is a CA key */
-	if ((keyopts = sshauthopt_parse(key_options, &reason)) == NULL) {
-		debug("%s: bad key options: %s", loc, reason);
-		auth_debug_add("%s: bad key options: %s", loc, reason);
-		goto out;
-	}
-	/* Ignore keys that don't match or incorrectly marked as CAs */
-	if (sshkey_is_cert(key)) {
-		/* Certificate; check signature key against CA */
-		if (!sshkey_equal(found, key->cert->signature_key) ||
-		    !keyopts->cert_authority)
-			goto out;
-	} else {
-		/* Plain key: check it against key found in file */
-		if (!sshkey_equal(found, key) || keyopts->cert_authority)
-			goto out;
-	}
-
-	/* We have a candidate key, perform authorisation checks */
-	if ((fp = sshkey_fingerprint(found,
-	    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
-		fatal_f("fingerprint failed");
-
-	debug("%s: matching %s found: %s %s", loc,
-	    sshkey_is_cert(key) ? "CA" : "key", sshkey_type(found), fp);
-
-	if (auth_authorise_keyopts(pw, keyopts,
-	    sshkey_is_cert(key), remote_ip, remote_host, loc) != 0) {
-		reason = "Refused by key options";
-		goto fail_reason;
-	}
-	/* That's all we need for plain keys. */
-	if (!sshkey_is_cert(key)) {
-		verbose("Accepted key %s %s found at %s",
-		    sshkey_type(found), fp, loc);
-		finalopts = keyopts;
-		keyopts = NULL;
-		goto success;
-	}
-
-	/*
-	 * Additional authorisation for certificates.
-	 */
-
-	/* Parse and check options present in certificate */
-	if ((certopts = sshauthopt_from_cert(key)) == NULL) {
-		reason = "Invalid certificate options";
-		goto fail_reason;
-	}
-	if (auth_authorise_keyopts(pw, certopts, 0,
-	    remote_ip, remote_host, loc) != 0) {
-		reason = "Refused by certificate options";
-		goto fail_reason;
-	}
-	if ((finalopts = sshauthopt_merge(keyopts, certopts, &reason)) == NULL)
-		goto fail_reason;
-
-	/*
-	 * If the user has specified a list of principals as
-	 * a key option, then prefer that list to matching
-	 * their username in the certificate principals list.
-	 */
-	if (keyopts->cert_principals != NULL &&
-	    !match_principals_option(keyopts->cert_principals, key->cert)) {
-		reason = "Certificate does not contain an authorized principal";
-		goto fail_reason;
-	}
-	if (sshkey_cert_check_authority_now(key, 0, 0, 0,
-	    keyopts->cert_principals == NULL ? pw->pw_name : NULL,
-	    &reason) != 0)
-		goto fail_reason;
-
-	verbose("Accepted certificate ID \"%s\" (serial %llu) "
-	    "signed by CA %s %s found at %s",
-	    key->cert->key_id,
-	    (unsigned long long)key->cert->serial,
-	    sshkey_type(found), fp, loc);
-
- success:
-	if (finalopts == NULL)
-		fatal_f("internal error: missing options");
-	if (authoptsp != NULL) {
-		*authoptsp = finalopts;
-		finalopts = NULL;
-	}
-	/* success */
-	ret = 0;
-	goto out;
-
- fail_reason:
-	error("%s", reason);
-	auth_debug_add("%s", reason);
- out:
-	free(fp);
-	sshauthopt_free(keyopts);
-	sshauthopt_free(certopts);
-	sshauthopt_free(finalopts);
-	sshkey_free(found);
-	return ret;
-}
-
-/*
- * Checks whether key is allowed in authorized_keys-format file,
- * returns 1 if the key is allowed or 0 otherwise.
- */
-static int
-check_authkeys_file(struct passwd *pw, FILE *f, char *file,
-    struct sshkey *key, const char *remote_ip,
-    const char *remote_host, struct sshauthopt **authoptsp)
-{
-	char *cp, *line = NULL, loc[256];
-	size_t linesize = 0;
-	int found_key = 0;
-	u_long linenum = 0, nonblank = 0;
-
-	if (authoptsp != NULL)
-		*authoptsp = NULL;
-
-	while (getline(&line, &linesize, f) != -1) {
-		linenum++;
-		/* Always consume entire file */
-		if (found_key)
-			continue;
-
-		/* Skip leading whitespace, empty and comment lines. */
-		cp = line;
-		skip_space(&cp);
-		if (!*cp || *cp == '\n' || *cp == '#')
-			continue;
-
-		nonblank++;
-		snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum);
-		if (check_authkey_line(pw, key, cp,
-		    remote_ip, remote_host, loc, authoptsp) == 0)
-			found_key = 1;
-	}
-	free(line);
-	debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum);
-	return found_key;
-}
-
 /* Authenticate a certificate key against TrustedUserCAKeys */
 static int
 user_cert_trusted_ca(struct passwd *pw, struct sshkey *key,
@@ -902,7 +603,7 @@ user_key_allowed2(struct passwd *pw, struct sshkey *key,
 
 	debug("trying public key file %s", file);
 	if ((f = auth_openkeyfile(file, pw, options.strict_modes)) != NULL) {
-		found_key = check_authkeys_file(pw, f, file,
+		found_key = auth_check_authkeys_file(pw, f, file,
 		    key, remote_ip, remote_host, authoptsp);
 		fclose(f);
 	}
@@ -1018,7 +719,7 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key,
 	uid_swapped = 1;
 	temporarily_use_uid(runas_pw);
 
-	ok = check_authkeys_file(user_pw, f,
+	ok = auth_check_authkeys_file(user_pw, f,
 	    options.authorized_keys_command, key, remote_ip,
 	    remote_host, authoptsp);
 
diff --git a/auth2-pubkeyfile.c b/auth2-pubkeyfile.c
new file mode 100644
index 00000000..a304d095
--- /dev/null
+++ b/auth2-pubkeyfile.c
@@ -0,0 +1,442 @@
+/* $OpenBSD: auth2-pubkeyfile.c,v 1.1 2022/05/27 05:02:46 djm Exp $ */
+/*
+ * Copyright (c) 2000 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2010 Damien Miller.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "ssh.h"
+#include "log.h"
+#include "misc.h"
+#include "compat.h"
+#include "sshkey.h"
+#include "digest.h"
+#include "hostfile.h"
+#include "auth.h"
+#include "auth-options.h"
+#include "authfile.h"
+#include "match.h"
+#include "ssherr.h"
+
+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)
+{
+	time_t now = time(NULL);
+	char buf[64];
+
+	/*
+	 * Check keys/principals file expiry time.
+	 * NB. validity interval in certificate is handled elsewhere.
+	 */
+	if (opts->valid_before && now > 0 &&
+	    opts->valid_before < (uint64_t)now) {
+		format_absolute_time(opts->valid_before, buf, sizeof(buf));
+		debug("%s: entry expired at %s", loc, buf);
+		auth_debug_add("%s: entry expired at %s", loc, buf);
+		return -1;
+	}
+	/* Consistency checks */
+	if (opts->cert_principals != NULL && !opts->cert_authority) {
+		debug("%s: principals on non-CA key", loc);
+		auth_debug_add("%s: principals on non-CA key", loc);
+		/* deny access */
+		return -1;
+	}
+	/* cert-authority flag isn't valid in authorized_principals files */
+	if (!allow_cert_authority && opts->cert_authority) {
+		debug("%s: cert-authority flag invalid here", loc);
+		auth_debug_add("%s: cert-authority flag invalid here", loc);
+		/* deny access */
+		return -1;
+	}
+
+	/* Perform from= checks */
+	if (opts->required_from_host_keys != NULL) {
+		switch (match_host_and_ip(remote_host, remote_ip,
+		    opts->required_from_host_keys )) {
+		case 1:
+			/* Host name matches. */
+			break;
+		case -1:
+		default:
+			debug("%s: invalid from criteria", loc);
+			auth_debug_add("%s: invalid from criteria", loc);
+			/* FALLTHROUGH */
+		case 0:
+			logit("%s: Authentication tried for %.100s with "
+			    "correct key but not from a permitted "
+			    "host (host=%.200s, ip=%.200s, required=%.200s).",
+			    loc, pw->pw_name, remote_host, remote_ip,
+			    opts->required_from_host_keys);
+			auth_debug_add("%s: Your host '%.200s' is not "
+			    "permitted to use this key for login.",
+			    loc, remote_host);
+			/* deny access */
+			return -1;
+		}
+	}
+	/* Check source-address restriction from certificate */
+	if (opts->required_from_host_cert != NULL) {
+		switch (addr_match_cidr_list(remote_ip,
+		    opts->required_from_host_cert)) {
+		case 1:
+			/* accepted */
+			break;
+		case -1:
+		default:
+			/* invalid */
+			error("%s: Certificate source-address invalid", loc);
+			/* FALLTHROUGH */
+		case 0:
+			logit("%s: Authentication tried for %.100s with valid "
+			    "certificate but not from a permitted source "
+			    "address (%.200s).", loc, pw->pw_name, remote_ip);
+			auth_debug_add("%s: Your address '%.200s' is not "
+			    "permitted to use this certificate for login.",
+			    loc, remote_ip);
+			return -1;
+		}
+	}
+	/*
+	 *
+	 * XXX this is spammy. We should report remotely only for keys
+	 *     that are successful in actual auth attempts, and not PK_OK
+	 *     tests.
+	 */
+	auth_log_authopts(loc, opts, 1);
+
+	return 0;
+}
+
+static int
+match_principals_option(const char *principal_list, struct sshkey_cert *cert)
+{
+	char *result;
+	u_int i;
+
+	/* XXX percent_expand() sequences for authorized_principals? */
+
+	for (i = 0; i < cert->nprincipals; i++) {
+		if ((result = match_list(cert->principals[i],
+		    principal_list, NULL)) != NULL) {
+			debug3("matched principal from key options \"%.100s\"",
+			    result);
+			free(result);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Process a single authorized_principals format line. Returns 0 and sets
+ * authoptsp is principal is authorised, -1 otherwise. "loc" is used as a
+ * log preamble for file/line information.
+ */
+int
+auth_check_principals_line(char *cp, const struct sshkey_cert *cert,
+    const char *loc, struct sshauthopt **authoptsp)
+{
+	u_int i, found = 0;
+	char *ep, *line_opts;
+	const char *reason = NULL;
+	struct sshauthopt *opts = NULL;
+
+	if (authoptsp != NULL)
+		*authoptsp = NULL;
+
+	/* Trim trailing whitespace. */
+	ep = cp + strlen(cp) - 1;
+	while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t'))
+		*ep-- = '\0';
+
+	/*
+	 * If the line has internal whitespace then assume it has
+	 * key options.
+	 */
+	line_opts = NULL;
+	if ((ep = strrchr(cp, ' ')) != NULL ||
+	    (ep = strrchr(cp, '\t')) != NULL) {
+		for (; *ep == ' ' || *ep == '\t'; ep++)
+			;
+		line_opts = cp;
+		cp = ep;
+	}
+	if ((opts = sshauthopt_parse(line_opts, &reason)) == NULL) {
+		debug("%s: bad principals options: %s", loc, reason);
+		auth_debug_add("%s: bad principals options: %s", loc, reason);
+		return -1;
+	}
+	/* Check principals in cert against those on line */
+	for (i = 0; i < cert->nprincipals; i++) {
+		if (strcmp(cp, cert->principals[i]) != 0)
+			continue;
+		debug3("%s: matched principal \"%.100s\"",
+		    loc, cert->principals[i]);
+		found = 1;
+	}
+	if (found && authoptsp != NULL) {
+		*authoptsp = opts;
+		opts = NULL;
+	}
+	sshauthopt_free(opts);
+	return found ? 0 : -1;
+}
+
+int
+auth_process_principals(FILE *f, const char *file,
+    const struct sshkey_cert *cert, struct sshauthopt **authoptsp)
+{
+	char loc[256], *line = NULL, *cp, *ep;
+	size_t linesize = 0;
+	u_long linenum = 0, nonblank = 0;
+	u_int found_principal = 0;
+
+	if (authoptsp != NULL)
+		*authoptsp = NULL;
+
+	while (getline(&line, &linesize, f) != -1) {
+		linenum++;
+		/* Always consume entire input */
+		if (found_principal)
+			continue;
+
+		/* Skip leading whitespace. */
+		for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
+			;
+		/* Skip blank and comment lines. */
+		if ((ep = strchr(cp, '#')) != NULL)
+			*ep = '\0';
+		if (!*cp || *cp == '\n')
+			continue;
+
+		nonblank++;
+		snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum);
+		if (auth_check_principals_line(cp, cert, loc, authoptsp) == 0)
+			found_principal = 1;
+	}
+	debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum);
+	free(line);
+	return found_principal;
+}
+
+/*
+ * Check a single line of an authorized_keys-format file. Returns 0 if key
+ * matches, -1 otherwise. Will return key/cert options via *authoptsp
+ * on success. "loc" is used as file/line location in log messages.
+ */
+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)
+{
+	int want_keytype = sshkey_is_cert(key) ? KEY_UNSPEC : key->type;
+	struct sshkey *found = NULL;
+	struct sshauthopt *keyopts = NULL, *certopts = NULL, *finalopts = NULL;
+	char *key_options = NULL, *fp = NULL;
+	const char *reason = NULL;
+	int ret = -1;
+
+	if (authoptsp != NULL)
+		*authoptsp = NULL;
+
+	if ((found = sshkey_new(want_keytype)) == NULL) {
+		debug3_f("keytype %d failed", want_keytype);
+		goto out;
+	}
+
+	/* XXX djm: peek at key type in line and skip if unwanted */
+
+	if (sshkey_read(found, &cp) != 0) {
+		/* no key?  check for options */
+		debug2("%s: check options: '%s'", loc, cp);
+		key_options = cp;
+		if (sshkey_advance_past_options(&cp) != 0) {
+			reason = "invalid key option string";
+			goto fail_reason;
+		}
+		skip_space(&cp);
+		if (sshkey_read(found, &cp) != 0) {
+			/* still no key?  advance to next line*/
+			debug2("%s: advance: '%s'", loc, cp);
+			goto out;
+		}
+	}
+	/* Parse key options now; we need to know if this is a CA key */
+	if ((keyopts = sshauthopt_parse(key_options, &reason)) == NULL) {
+		debug("%s: bad key options: %s", loc, reason);
+		auth_debug_add("%s: bad key options: %s", loc, reason);
+		goto out;
+	}
+	/* Ignore keys that don't match or incorrectly marked as CAs */
+	if (sshkey_is_cert(key)) {
+		/* Certificate; check signature key against CA */
+		if (!sshkey_equal(found, key->cert->signature_key) ||
+		    !keyopts->cert_authority)
+			goto out;
+	} else {
+		/* Plain key: check it against key found in file */
+		if (!sshkey_equal(found, key) || keyopts->cert_authority)
+			goto out;
+	}
+
+	/* We have a candidate key, perform authorisation checks */
+	if ((fp = sshkey_fingerprint(found,
+	    SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL)
+		fatal_f("fingerprint failed");
+
+	debug("%s: matching %s found: %s %s", loc,
+	    sshkey_is_cert(key) ? "CA" : "key", sshkey_type(found), fp);
+
+	if (auth_authorise_keyopts(pw, keyopts,
+	    sshkey_is_cert(key), remote_ip, remote_host, loc) != 0) {
+		reason = "Refused by key options";
+		goto fail_reason;
+	}
+	/* That's all we need for plain keys. */
+	if (!sshkey_is_cert(key)) {
+		verbose("Accepted key %s %s found at %s",
+		    sshkey_type(found), fp, loc);
+		finalopts = keyopts;
+		keyopts = NULL;
+		goto success;
+	}
+
+	/*
+	 * Additional authorisation for certificates.
+	 */
+
+	/* Parse and check options present in certificate */
+	if ((certopts = sshauthopt_from_cert(key)) == NULL) {
+		reason = "Invalid certificate options";
+		goto fail_reason;
+	}
+	if (auth_authorise_keyopts(pw, certopts, 0,
+	    remote_ip, remote_host, loc) != 0) {
+		reason = "Refused by certificate options";
+		goto fail_reason;
+	}
+	if ((finalopts = sshauthopt_merge(keyopts, certopts, &reason)) == NULL)
+		goto fail_reason;
+
+	/*
+	 * If the user has specified a list of principals as
+	 * a key option, then prefer that list to matching
+	 * their username in the certificate principals list.
+	 */
+	if (keyopts->cert_principals != NULL &&
+	    !match_principals_option(keyopts->cert_principals, key->cert)) {
+		reason = "Certificate does not contain an authorized principal";
+		goto fail_reason;
+	}
+	if (sshkey_cert_check_authority_now(key, 0, 0, 0,
+	    keyopts->cert_principals == NULL ? pw->pw_name : NULL,
+	    &reason) != 0)
+		goto fail_reason;
+
+	verbose("Accepted certificate ID \"%s\" (serial %llu) "
+	    "signed by CA %s %s found at %s",
+	    key->cert->key_id,
+	    (unsigned long long)key->cert->serial,
+	    sshkey_type(found), fp, loc);
+
+ success:
+	if (finalopts == NULL)
+		fatal_f("internal error: missing options");
+	if (authoptsp != NULL) {
+		*authoptsp = finalopts;
+		finalopts = NULL;
+	}
+	/* success */
+	ret = 0;
+	goto out;
+
+ fail_reason:
+	error("%s", reason);
+	auth_debug_add("%s", reason);
+ out:
+	free(fp);
+	sshauthopt_free(keyopts);
+	sshauthopt_free(certopts);
+	sshauthopt_free(finalopts);
+	sshkey_free(found);
+	return ret;
+}
+
+/*
+ * Checks whether key is allowed in authorized_keys-format file,
+ * returns 1 if the key is allowed or 0 otherwise.
+ */
+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)
+{
+	char *cp, *line = NULL, loc[256];
+	size_t linesize = 0;
+	int found_key = 0;
+	u_long linenum = 0, nonblank = 0;
+
+	if (authoptsp != NULL)
+		*authoptsp = NULL;
+
+	while (getline(&line, &linesize, f) != -1) {
+		linenum++;
+		/* Always consume entire file */
+		if (found_key)
+			continue;
+
+		/* Skip leading whitespace, empty and comment lines. */
+		cp = line;
+		skip_space(&cp);
+		if (!*cp || *cp == '\n' || *cp == '#')
+			continue;
+
+		nonblank++;
+		snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum);
+		if (auth_check_authkey_line(pw, key, cp,
+		    remote_ip, remote_host, loc, authoptsp) == 0)
+			found_key = 1;
+	}
+	free(line);
+	debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum);
+	return found_key;
+}
+
+

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list