split the low-level file handling functions out from auth2-pubkey.c
authordjm <djm@openbsd.org>
Fri, 27 May 2022 05:02:46 +0000 (05:02 +0000)
committerdjm <djm@openbsd.org>
Fri, 27 May 2022 05:02:46 +0000 (05:02 +0000)
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@

usr.bin/ssh/auth.c
usr.bin/ssh/auth.h
usr.bin/ssh/auth2-pubkey.c
usr.bin/ssh/auth2-pubkeyfile.c [new file with mode: 0644]
usr.bin/ssh/sshd/Makefile

index 9fff5c1..93b5634 100644 (file)
@@ -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.
  *
@@ -833,95 +833,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;
-}
index ea2ca7f..0ae5c07 100644 (file)
@@ -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>
 
 #include <bsd_auth.h>
 #ifdef KRB5
@@ -39,6 +40,7 @@ struct passwd;
 struct ssh;
 struct sshbuf;
 struct sshkey;
+struct sshkey_cert;
 struct sshauthopt;
 
 typedef struct Authctxt Authctxt;
@@ -195,8 +197,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 */
@@ -207,4 +207,16 @@ 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 **);
+
 #endif
index 1a479fb..0f44732 100644 (file)
@@ -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
 
 
 #include <sys/types.h>
-#include <sys/stat.h>
 
 #include <stdlib.h>
 #include <errno.h>
-#include <fcntl.h>
 #include <paths.h>
 #include <pwd.h>
 #include <signal.h>
@@ -64,7 +63,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"
@@ -318,120 +316,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)
@@ -448,7 +332,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;
@@ -564,7 +448,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;
@@ -592,189 +476,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,
@@ -899,7 +600,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);
        }
@@ -1015,7 +716,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/usr.bin/ssh/auth2-pubkeyfile.c b/usr.bin/ssh/auth2-pubkeyfile.c
new file mode 100644 (file)
index 0000000..ac0ad73
--- /dev/null
@@ -0,0 +1,441 @@
+/* $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 <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;
+}
+
+
index 0768b2f..3645526 100644 (file)
@@ -1,11 +1,12 @@
-#      $OpenBSD: Makefile,v 1.105 2021/01/09 12:10:02 dtucker Exp $
+#      $OpenBSD: Makefile,v 1.106 2022/05/27 05:02:46 djm Exp $
 
 .PATH:         ${.CURDIR}/..
 
 SRCS=  sshd.c auth-rhosts.c auth-passwd.c sshpty.c sshlogin.c servconf.c \
        serverloop.c auth.c auth2.c auth-options.c session.c auth2-chall.c \
        groupaccess.c auth-bsdauth.c auth2-hostbased.c auth2-kbdint.c \
-       auth2-none.c auth2-passwd.c auth2-pubkey.c monitor.c monitor_wrap.c \
+       auth2-none.c auth2-passwd.c auth2-pubkey.c auth2-pubkeyfile.c \
+       monitor.c monitor_wrap.c \
        sftp-server.c sftp-common.c sftp-realpath.c sandbox-pledge.c srclimit.c
 SRCS+= authfd.c compat.c dns.c fatal.c hostfile.c readpass.c utf8.c uidswap.c
 SRCS+= ${SRCS_BASE} ${SRCS_KEX} ${SRCS_KEXS} ${SRCS_KEY} ${SRCS_KEYP} \