-/* $OpenBSD: auth2-pubkey.c,v 1.29 2011/05/23 03:30:07 djm Exp $ */
+/* $OpenBSD: auth2-pubkey.c,v 1.47 2015/02/17 00:14:05 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
*
#include <sys/types.h>
#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
#include <fcntl.h>
+#ifdef HAVE_PATHS_H
+# include <paths.h>
+#endif
#include <pwd.h>
+#include <signal.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
+#include <limits.h>
#include "xmalloc.h"
#include "ssh.h"
#include "packet.h"
#include "buffer.h"
#include "log.h"
+#include "misc.h"
#include "servconf.h"
#include "compat.h"
#include "key.h"
#include "ssh-gss.h"
#endif
#include "monitor_wrap.h"
-#include "misc.h"
#include "authfile.h"
#include "match.h"
{
Buffer b;
Key *key = NULL;
- char *pkalg;
+ char *pkalg, *userstyle;
u_char *pkblob, *sig;
u_int alen, blen, slen;
int have_sig, pktype;
"(received %d, expected %d)", key->type, pktype);
goto done;
}
+ if (key_type_plain(key->type) == KEY_RSA &&
+ (datafellows & SSH_BUG_RSASIGMD5) != 0) {
+ logit("Refusing RSA key because client uses unsafe "
+ "signature scheme");
+ goto done;
+ }
+ if (auth2_userkey_already_used(authctxt, key)) {
+ logit("refusing previously-used %s key", key_type(key));
+ goto done;
+ }
+ if (match_pattern_list(sshkey_ssh_name(key), options.pubkey_key_types,
+ strlen(options.pubkey_key_types), 0) != 1) {
+ logit("%s: key type %s not in PubkeyAcceptedKeyTypes",
+ __func__, sshkey_ssh_name(key));
+ goto done;
+ }
+
if (have_sig) {
sig = packet_get_string(&slen);
packet_check_eom();
}
/* reconstruct packet */
buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST);
- buffer_put_cstring(&b, authctxt->user);
+ xasprintf(&userstyle, "%s%s%s", authctxt->user,
+ authctxt->style ? ":" : "",
+ authctxt->style ? authctxt->style : "");
+ buffer_put_cstring(&b, userstyle);
+ free(userstyle);
buffer_put_cstring(&b,
datafellows & SSH_BUG_PKSERVICE ?
"ssh-userauth" :
#ifdef DEBUG_PK
buffer_dump(&b);
#endif
+ pubkey_auth_info(authctxt, key, NULL);
+
/* test for correct signature */
authenticated = 0;
if (PRIVSEP(user_key_allowed(authctxt->pw, key)) &&
PRIVSEP(key_verify(key, sig, slen, buffer_ptr(&b),
- buffer_len(&b))) == 1)
+ buffer_len(&b))) == 1) {
authenticated = 1;
+ /* Record the successful key to prevent reuse */
+ auth2_record_userkey(authctxt, key);
+ key = NULL; /* Don't free below */
+ }
buffer_free(&b);
- xfree(sig);
+ free(sig);
} else {
debug("test whether pkalg/pkblob are acceptable");
packet_check_eom();
debug2("userauth_pubkey: authenticated %d pkalg %s", authenticated, pkalg);
if (key != NULL)
key_free(key);
- xfree(pkalg);
- xfree(pkblob);
+ free(pkalg);
+ free(pkblob);
return authenticated;
}
+void
+pubkey_auth_info(Authctxt *authctxt, const Key *key, const char *fmt, ...)
+{
+ char *fp, *extra;
+ va_list ap;
+ int i;
+
+ extra = NULL;
+ if (fmt != NULL) {
+ va_start(ap, fmt);
+ i = vasprintf(&extra, fmt, ap);
+ va_end(ap);
+ if (i < 0 || extra == NULL)
+ fatal("%s: vasprintf failed", __func__);
+ }
+
+ if (key_is_cert(key)) {
+ fp = sshkey_fingerprint(key->cert->signature_key,
+ options.fingerprint_hash, SSH_FP_DEFAULT);
+ auth_info(authctxt, "%s ID %s (serial %llu) CA %s %s%s%s",
+ key_type(key), key->cert->key_id,
+ (unsigned long long)key->cert->serial,
+ key_type(key->cert->signature_key),
+ fp == NULL ? "(null)" : fp,
+ extra == NULL ? "" : ", ", extra == NULL ? "" : extra);
+ free(fp);
+ } else {
+ fp = sshkey_fingerprint(key, options.fingerprint_hash,
+ SSH_FP_DEFAULT);
+ auth_info(authctxt, "%s %s%s%s", key_type(key),
+ fp == NULL ? "(null)" : fp,
+ extra == NULL ? "" : ", ", extra == NULL ? "" : extra);
+ free(fp);
+ }
+ free(extra);
+}
+
static int
-match_principals_option(const char *principal_list, struct KeyCert *cert)
+match_principals_option(const char *principal_list, struct sshkey_cert *cert)
{
char *result;
u_int i;
principal_list, NULL)) != NULL) {
debug3("matched principal from key options \"%.100s\"",
result);
- xfree(result);
+ free(result);
return 1;
}
}
}
static int
-match_principals_file(char *file, struct passwd *pw, struct KeyCert *cert)
+match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert)
{
FILE *f;
char line[SSH_MAX_PUBKEY_BYTES], *cp, *ep, *line_opts;
}
for (i = 0; i < cert->nprincipals; i++) {
if (strcmp(cp, cert->principals[i]) == 0) {
- debug3("matched principal from file \"%.100s\"",
- cert->principals[i]);
+ debug3("matched principal \"%.100s\" "
+ "from file \"%s\" on line %lu",
+ cert->principals[i], file, linenum);
if (auth_parse_options(pw, line_opts,
file, linenum) != 1)
continue;
fclose(f);
restore_uid();
return 0;
-}
+}
-/* return 1 if user allows given key */
+/*
+ * Checks whether key is allowed in authorized_keys-format file,
+ * returns 1 if the key is allowed or 0 otherwise.
+ */
static int
-user_key_allowed2(struct passwd *pw, Key *key, char *file)
+check_authkeys_file(FILE *f, char *file, Key* key, struct passwd *pw)
{
char line[SSH_MAX_PUBKEY_BYTES];
const char *reason;
int found_key = 0;
- FILE *f;
u_long linenum = 0;
Key *found;
char *fp;
- /* Temporarily use the user's uid. */
- temporarily_use_uid(pw);
-
- debug("trying public key file %s", file);
- f = auth_openkeyfile(file, pw, options.strict_modes);
-
- if (!f) {
- restore_uid();
- return 0;
- }
-
found_key = 0;
- found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type);
+ found = NULL;
while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
char *cp, *key_options = NULL;
-
+ if (found != NULL)
+ key_free(found);
+ found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type);
auth_clear_options();
/* Skip leading whitespace, empty and comment lines. */
continue;
if (!key_is_cert_authority)
continue;
- fp = key_fingerprint(found, SSH_FP_MD5,
- SSH_FP_HEX);
+ if ((fp = sshkey_fingerprint(found,
+ options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+ continue;
debug("matching CA found: file %s, line %lu, %s %s",
file, linenum, key_type(found), fp);
/*
reason = "Certificate does not contain an "
"authorized principal";
fail_reason:
- xfree(fp);
+ free(fp);
error("%s", reason);
auth_debug_add("%s", reason);
continue;
&reason) != 0)
goto fail_reason;
if (auth_cert_options(key, pw) != 0) {
- xfree(fp);
+ free(fp);
continue;
}
verbose("Accepted certificate ID \"%s\" "
"signed by %s CA %s via %s", key->cert->key_id,
key_type(found), fp, file);
- xfree(fp);
+ free(fp);
found_key = 1;
break;
} else if (key_equal(found, key)) {
continue;
if (key_is_cert_authority)
continue;
+ if ((fp = sshkey_fingerprint(found,
+ options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+ continue;
+ debug("matching key found: file %s, line %lu %s %s",
+ file, linenum, key_type(found), fp);
+ free(fp);
found_key = 1;
- debug("matching key found: file %s, line %lu",
- file, linenum);
- fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX);
- verbose("Found matching %s key: %s",
- key_type(found), fp);
- xfree(fp);
break;
}
}
- restore_uid();
- fclose(f);
- key_free(found);
+ if (found != NULL)
+ key_free(found);
if (!found_key)
debug2("key not found");
return found_key;
if (!key_is_cert(key) || options.trusted_user_ca_keys == NULL)
return 0;
- ca_fp = key_fingerprint(key->cert->signature_key,
- SSH_FP_MD5, SSH_FP_HEX);
+ if ((ca_fp = sshkey_fingerprint(key->cert->signature_key,
+ options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+ return 0;
- if (key_in_file(key->cert->signature_key,
- options.trusted_user_ca_keys, 1) != 1) {
+ if (sshkey_in_file(key->cert->signature_key,
+ options.trusted_user_ca_keys, 1, 0) != 0) {
debug2("%s: CA %s %s is not listed in %s", __func__,
key_type(key->cert->signature_key), ca_fp,
options.trusted_user_ca_keys);
ret = 1;
out:
- if (principals_file != NULL)
- xfree(principals_file);
- if (ca_fp != NULL)
- xfree(ca_fp);
+ free(principals_file);
+ free(ca_fp);
return ret;
}
-/* check whether given key is in .ssh/authorized_keys* */
+/*
+ * Checks whether key is allowed in file.
+ * returns 1 if the key is allowed or 0 otherwise.
+ */
+static int
+user_key_allowed2(struct passwd *pw, Key *key, char *file)
+{
+ FILE *f;
+ int found_key = 0;
+
+ /* Temporarily use the user's uid. */
+ temporarily_use_uid(pw);
+
+ debug("trying public key file %s", file);
+ if ((f = auth_openkeyfile(file, pw, options.strict_modes)) != NULL) {
+ found_key = check_authkeys_file(f, file, key, pw);
+ fclose(f);
+ }
+
+ restore_uid();
+ return found_key;
+}
+
+/*
+ * Checks whether key is allowed in output of command.
+ * returns 1 if the key is allowed or 0 otherwise.
+ */
+static int
+user_key_command_allowed2(struct passwd *user_pw, Key *key)
+{
+ FILE *f;
+ int ok, found_key = 0;
+ struct passwd *pw;
+ struct stat st;
+ int status, devnull, p[2], i;
+ pid_t pid;
+ char *username, errmsg[512];
+
+ if (options.authorized_keys_command == NULL ||
+ options.authorized_keys_command[0] != '/')
+ return 0;
+
+ if (options.authorized_keys_command_user == NULL) {
+ error("No user for AuthorizedKeysCommand specified, skipping");
+ return 0;
+ }
+
+ username = percent_expand(options.authorized_keys_command_user,
+ "u", user_pw->pw_name, (char *)NULL);
+ pw = getpwnam(username);
+ if (pw == NULL) {
+ error("AuthorizedKeysCommandUser \"%s\" not found: %s",
+ username, strerror(errno));
+ free(username);
+ return 0;
+ }
+ free(username);
+
+ temporarily_use_uid(pw);
+
+ if (stat(options.authorized_keys_command, &st) < 0) {
+ error("Could not stat AuthorizedKeysCommand \"%s\": %s",
+ options.authorized_keys_command, strerror(errno));
+ goto out;
+ }
+ if (auth_secure_path(options.authorized_keys_command, &st, NULL, 0,
+ errmsg, sizeof(errmsg)) != 0) {
+ error("Unsafe AuthorizedKeysCommand: %s", errmsg);
+ goto out;
+ }
+
+ if (pipe(p) != 0) {
+ error("%s: pipe: %s", __func__, strerror(errno));
+ goto out;
+ }
+
+ debug3("Running AuthorizedKeysCommand: \"%s %s\" as \"%s\"",
+ options.authorized_keys_command, user_pw->pw_name, pw->pw_name);
+
+ /*
+ * Don't want to call this in the child, where it can fatal() and
+ * run cleanup_exit() code.
+ */
+ restore_uid();
+
+ switch ((pid = fork())) {
+ case -1: /* error */
+ error("%s: fork: %s", __func__, strerror(errno));
+ close(p[0]);
+ close(p[1]);
+ return 0;
+ case 0: /* child */
+ for (i = 0; i < NSIG; i++)
+ signal(i, SIG_DFL);
+
+ if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+ error("%s: open %s: %s", __func__, _PATH_DEVNULL,
+ strerror(errno));
+ _exit(1);
+ }
+ /* Keep stderr around a while longer to catch errors */
+ if (dup2(devnull, STDIN_FILENO) == -1 ||
+ dup2(p[1], STDOUT_FILENO) == -1) {
+ error("%s: dup2: %s", __func__, strerror(errno));
+ _exit(1);
+ }
+ closefrom(STDERR_FILENO + 1);
+
+ /* Don't use permanently_set_uid() here to avoid fatal() */
+ if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) {
+ error("setresgid %u: %s", (u_int)pw->pw_gid,
+ strerror(errno));
+ _exit(1);
+ }
+ if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) {
+ error("setresuid %u: %s", (u_int)pw->pw_uid,
+ strerror(errno));
+ _exit(1);
+ }
+ /* stdin is pointed to /dev/null at this point */
+ if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) {
+ error("%s: dup2: %s", __func__, strerror(errno));
+ _exit(1);
+ }
+
+ execl(options.authorized_keys_command,
+ options.authorized_keys_command, user_pw->pw_name, NULL);
+
+ error("AuthorizedKeysCommand %s exec failed: %s",
+ options.authorized_keys_command, strerror(errno));
+ _exit(127);
+ default: /* parent */
+ break;
+ }
+
+ temporarily_use_uid(pw);
+
+ close(p[1]);
+ if ((f = fdopen(p[0], "r")) == NULL) {
+ error("%s: fdopen: %s", __func__, strerror(errno));
+ close(p[0]);
+ /* Don't leave zombie child */
+ kill(pid, SIGTERM);
+ while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
+ ;
+ goto out;
+ }
+ ok = check_authkeys_file(f, options.authorized_keys_command, key, pw);
+ fclose(f);
+
+ while (waitpid(pid, &status, 0) == -1) {
+ if (errno != EINTR) {
+ error("%s: waitpid: %s", __func__, strerror(errno));
+ goto out;
+ }
+ }
+ if (WIFSIGNALED(status)) {
+ error("AuthorizedKeysCommand %s exited on signal %d",
+ options.authorized_keys_command, WTERMSIG(status));
+ goto out;
+ } else if (WEXITSTATUS(status) != 0) {
+ error("AuthorizedKeysCommand %s returned status %d",
+ options.authorized_keys_command, WEXITSTATUS(status));
+ goto out;
+ }
+ found_key = ok;
+ out:
+ restore_uid();
+ return found_key;
+}
+
+/*
+ * Check whether key authenticates and authorises the user.
+ */
int
user_key_allowed(struct passwd *pw, Key *key)
{
if (success)
return success;
+ success = user_key_command_allowed2(pw, key);
+ if (success > 0)
+ return success;
+
for (i = 0; !success && i < options.num_authkeys_files; i++) {
+
+ if (strcasecmp(options.authorized_keys_files[i], "none") == 0)
+ continue;
file = expand_authorized_keys(
options.authorized_keys_files[i], pw);
+
success = user_key_allowed2(pw, key, file);
- xfree(file);
+ free(file);
}
return success;
}
+/* Records a public key in the list of previously-successful keys */
+void
+auth2_record_userkey(Authctxt *authctxt, struct sshkey *key)
+{
+ struct sshkey **tmp;
+
+ if (authctxt->nprev_userkeys >= INT_MAX ||
+ (tmp = reallocarray(authctxt->prev_userkeys,
+ authctxt->nprev_userkeys + 1, sizeof(*tmp))) == NULL)
+ fatal("%s: reallocarray failed", __func__);
+ authctxt->prev_userkeys = tmp;
+ authctxt->prev_userkeys[authctxt->nprev_userkeys] = key;
+ authctxt->nprev_userkeys++;
+}
+
+/* Checks whether a key has already been used successfully for authentication */
+int
+auth2_userkey_already_used(Authctxt *authctxt, struct sshkey *key)
+{
+ u_int i;
+
+ for (i = 0; i < authctxt->nprev_userkeys; i++) {
+ if (sshkey_equal_public(key, authctxt->prev_userkeys[i])) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
Authmethod method_pubkey = {
"publickey",
userauth_pubkey,