version 1.114, 2022/05/27 05:01:25 |
version 1.115, 2022/05/27 05:02:46 |
|
|
/* $OpenBSD$ */ |
/* $OpenBSD$ */ |
/* |
/* |
* Copyright (c) 2000 Markus Friedl. All rights reserved. |
* 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 |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* modification, are permitted provided that the following conditions |
|
|
|
|
|
|
#include <sys/types.h> |
#include <sys/types.h> |
#include <sys/stat.h> |
|
|
|
#include <stdlib.h> |
#include <stdlib.h> |
#include <errno.h> |
#include <errno.h> |
#include <fcntl.h> |
|
#include <paths.h> |
#include <paths.h> |
#include <pwd.h> |
#include <pwd.h> |
#include <signal.h> |
#include <signal.h> |
|
|
#include "authfile.h" |
#include "authfile.h" |
#include "match.h" |
#include "match.h" |
#include "ssherr.h" |
#include "ssherr.h" |
#include "kex.h" |
|
#include "channels.h" /* XXX for session.h */ |
#include "channels.h" /* XXX for session.h */ |
#include "session.h" /* XXX for child_set_env(); refactor? */ |
#include "session.h" /* XXX for child_set_env(); refactor? */ |
#include "sk-api.h" |
#include "sk-api.h" |
|
|
} |
} |
|
|
static int |
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, |
match_principals_file(struct passwd *pw, char *file, |
struct sshkey_cert *cert, struct sshauthopt **authoptsp) |
struct sshkey_cert *cert, struct sshauthopt **authoptsp) |
{ |
{ |
|
|
restore_uid(); |
restore_uid(); |
return 0; |
return 0; |
} |
} |
success = process_principals(f, file, cert, authoptsp); |
success = auth_process_principals(f, file, cert, authoptsp); |
fclose(f); |
fclose(f); |
restore_uid(); |
restore_uid(); |
return success; |
return success; |
|
|
uid_swapped = 1; |
uid_swapped = 1; |
temporarily_use_uid(runas_pw); |
temporarily_use_uid(runas_pw); |
|
|
ok = process_principals(f, "(command)", cert, authoptsp); |
ok = auth_process_principals(f, "(command)", cert, authoptsp); |
|
|
fclose(f); |
fclose(f); |
f = NULL; |
f = NULL; |
|
|
return found_principal; |
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 */ |
/* Authenticate a certificate key against TrustedUserCAKeys */ |
static int |
static int |
user_cert_trusted_ca(struct passwd *pw, struct sshkey *key, |
user_cert_trusted_ca(struct passwd *pw, struct sshkey *key, |
|
|
|
|
debug("trying public key file %s", file); |
debug("trying public key file %s", file); |
if ((f = auth_openkeyfile(file, pw, options.strict_modes)) != NULL) { |
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); |
key, remote_ip, remote_host, authoptsp); |
fclose(f); |
fclose(f); |
} |
} |
|
|
uid_swapped = 1; |
uid_swapped = 1; |
temporarily_use_uid(runas_pw); |
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, |
options.authorized_keys_command, key, remote_ip, |
remote_host, authoptsp); |
remote_host, authoptsp); |
|
|