diff --git a/Makefile.in b/Makefile.in index 7250d3f31..3c2856829 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 57ade8db7..9ad9034aa 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 a52ba7c2d..b8eec4a6a 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 +#include #ifdef HAVE_LOGIN_CAP #include @@ -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 2f58a138c..952af119e 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 -#include #include #include -#include #ifdef HAVE_PATHS_H # include #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 000000000..a304d0953 --- /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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} + +