aurweb/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch
Lukas Fleischer ef1f3798a0 Update the OpenSSH patch
Use the latest version of Damien Miller's patch to extend the parameters
to the AuthorizedKeysCommand.

Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
2015-04-11 14:08:30 +02:00

1094 lines
32 KiB
Diff

From 6423ae83d38535687d52097b7854b3c81151fe34 Mon Sep 17 00:00:00 2001
From: Lukas Fleischer <lfleischer@archlinux.org>
Date: Sat, 11 Apr 2015 12:57:46 +0200
Subject: [PATCH] Patch sshd for the AUR
* Apply the latest version of Damien Miller's patch to extend the
parameters to the AuthorizedKeysCommand.
* Remove the secure path check for the AuthorizedKeysCommand. We are
running the sshd under a non-privileged user who has as little
permissions as possible. In particular, he does not own the directory
that contains the scripts for the Git backend.
* Prevent from running the sshd as root.
Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
---
auth2-pubkey.c | 530 +++++++++++++++++++++++++++++++++++++++++++--------------
servconf.c | 35 ++++
servconf.h | 8 +-
ssh.c | 5 +
sshd.c | 5 +
sshd_config.5 | 54 +++++-
sshkey.c | 172 +++++++++++--------
sshkey.h | 1 +
8 files changed, 606 insertions(+), 204 deletions(-)
diff --git a/auth2-pubkey.c b/auth2-pubkey.c
index d943efa..2ce0a4b 100644
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -65,6 +65,9 @@
#include "monitor_wrap.h"
#include "authfile.h"
#include "match.h"
+#include "ssherr.h"
+#include "channels.h" /* XXX for session.h */
+#include "session.h" /* XXX for child_set_env(); refactor? */
/* import */
extern ServerOptions options;
@@ -248,6 +251,227 @@ pubkey_auth_info(Authctxt *authctxt, const Key *key, const char *fmt, ...)
free(extra);
}
+/*
+ * Splits 's' into an argument vector. Handles quoted string and basic
+ * escape characters (\\, \", \'). Caller must free the argument vector
+ * and its members.
+ */
+static int
+split_argv(const char *s, int *argcp, char ***argvp)
+{
+ int r = SSH_ERR_INTERNAL_ERROR;
+ int argc = 0, quote, i, j;
+ char *arg, **argv = xcalloc(1, sizeof(*argv));
+
+ *argvp = NULL;
+ *argcp = 0;
+
+ for (i = 0; s[i] != '\0'; i++) {
+ /* Skip leading whitespace */
+ if (s[i] == ' ' || s[i] == '\t')
+ continue;
+
+ /* Start of a token */
+ quote = 0;
+ if (s[i] == '\\' &&
+ (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\'))
+ i++;
+ else if (s[i] == '\'' || s[i] == '"')
+ quote = s[i++];
+
+ argv = xrealloc(argv, (argc + 2), sizeof(*argv));
+ arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1);
+ argv[argc] = NULL;
+
+ /* Copy the token in, removing escapes */
+ for (j = 0; s[i] != '\0'; i++) {
+ if (s[i] == '\\') {
+ if (s[i + 1] == '\'' ||
+ s[i + 1] == '\"' ||
+ s[i + 1] == '\\') {
+ i++; /* Skip '\' */
+ arg[j++] = s[i];
+ } else {
+ /* Unrecognised escape */
+ arg[j++] = s[i];
+ }
+ } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t'))
+ break; /* done */
+ else if (quote != 0 && s[i] == quote)
+ break; /* done */
+ else
+ arg[j++] = s[i];
+ }
+ if (s[i] == '\0') {
+ if (quote != 0) {
+ /* Ran out of string looking for close quote */
+ r = SSH_ERR_INVALID_FORMAT;
+ goto out;
+ }
+ break;
+ }
+ }
+ /* Success */
+ *argcp = argc;
+ *argvp = argv;
+ argc = 0;
+ argv = NULL;
+ r = 0;
+ out:
+ if (argc != 0 && argv != NULL) {
+ for (i = 0; i < argc; i++)
+ free(argv[i]);
+ free(argv);
+ }
+ return r;
+}
+
+/*
+ * Runs command in a subprocess. Returns pid on success and a FILE* to the
+ * subprocess' stdout or 0 on failure.
+ * NB. "command" is only used for logging.
+ */
+static pid_t
+subprocess(const char *tag, struct passwd *pw, const char *command,
+ int ac, char **av, FILE **child)
+{
+ FILE *f;
+ struct stat st;
+ int devnull, p[2], i;
+ pid_t pid;
+ char *cp, errmsg[512];
+ u_int envsize;
+ char **child_env;
+
+ *child = NULL;
+
+ debug3("%s: %s command \"%s\" running as %s", __func__,
+ tag, command, pw->pw_name);
+
+ /* Verify the path exists and is safe-ish to execute */
+ if (*av[0] != '/') {
+ error("%s path is not absolute", tag);
+ return 0;
+ }
+ temporarily_use_uid(pw);
+ if (stat(av[0], &st) < 0) {
+ error("Could not stat %s \"%s\": %s", tag,
+ av[0], strerror(errno));
+ restore_uid();
+ return 0;
+ }
+
+ /*
+ * Run the command; stderr is left in place, stdout is the
+ * authorized_keys output.
+ */
+ if (pipe(p) != 0) {
+ error("%s: pipe: %s", tag, strerror(errno));
+ restore_uid();
+ return 0;
+ }
+
+ /*
+ * 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", tag, strerror(errno));
+ close(p[0]);
+ close(p[1]);
+ return 0;
+ case 0: /* child */
+ /* Prepare a minimal environment for the child. */
+ envsize = 5;
+ child_env = xcalloc(sizeof(*child_env), envsize);
+ child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH);
+ child_set_env(&child_env, &envsize, "USER", pw->pw_name);
+ child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name);
+ child_set_env(&child_env, &envsize, "HOME", pw->pw_dir);
+ if ((cp = getenv("LANG")) != NULL)
+ child_set_env(&child_env, &envsize, "LANG", cp);
+
+ for (i = 0; i < NSIG; i++)
+ signal(i, SIG_DFL);
+
+ if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+ error("%s: open %s: %s", tag, _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", tag, 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("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid,
+ strerror(errno));
+ _exit(1);
+ }
+ if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) {
+ error("%s: setresuid %u: %s", tag, (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", tag, strerror(errno));
+ _exit(1);
+ }
+
+ execve(av[0], av, child_env);
+ error("%s exec \"%s\": %s", tag, command, strerror(errno));
+ _exit(127);
+ default: /* parent */
+ break;
+ }
+
+ close(p[1]);
+ if ((f = fdopen(p[0], "r")) == NULL) {
+ error("%s: fdopen: %s", tag, strerror(errno));
+ close(p[0]);
+ /* Don't leave zombie child */
+ kill(pid, SIGTERM);
+ while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
+ ;
+ return 0;
+ }
+ /* Success */
+ debug3("%s: %s pid %ld", __func__, tag, (long)pid);
+ *child = f;
+ return pid;
+}
+
+/* Returns 0 if pid exited cleanly, non-zero otherwise */
+static int
+exited_cleanly(pid_t pid, const char *tag, const char *cmd)
+{
+ int status;
+
+ while (waitpid(pid, &status, 0) == -1) {
+ if (errno != EINTR) {
+ error("%s: waitpid: %s", tag, strerror(errno));
+ return -1;
+ }
+ }
+ if (WIFSIGNALED(status)) {
+ error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status));
+ return -1;
+ } else if (WEXITSTATUS(status) != 0) {
+ error("%s %s failed, status %d", tag, cmd, WEXITSTATUS(status));
+ return -1;
+ }
+ return 0;
+}
+
static int
match_principals_option(const char *principal_list, struct sshkey_cert *cert)
{
@@ -269,19 +493,13 @@ match_principals_option(const char *principal_list, struct sshkey_cert *cert)
}
static int
-match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert)
+process_principals(FILE *f, char *file, struct passwd *pw,
+ struct sshkey_cert *cert)
{
- FILE *f;
char line[SSH_MAX_PUBKEY_BYTES], *cp, *ep, *line_opts;
u_long linenum = 0;
u_int i;
- temporarily_use_uid(pw);
- debug("trying authorized principals file %s", file);
- if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) {
- restore_uid();
- return 0;
- }
while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
/* Skip leading whitespace. */
for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
@@ -309,24 +527,119 @@ match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert)
}
for (i = 0; i < cert->nprincipals; i++) {
if (strcmp(cp, cert->principals[i]) == 0) {
- debug3("matched principal \"%.100s\" "
- "from file \"%s\" on line %lu",
- cert->principals[i], file, linenum);
+ debug3("%s:%lu: matched principal \"%.100s\"",
+ file == NULL ? "(command)" : file,
+ linenum, cert->principals[i]);
if (auth_parse_options(pw, line_opts,
file, linenum) != 1)
continue;
- fclose(f);
- restore_uid();
return 1;
}
}
}
+ return 0;
+}
+
+static int
+match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert)
+{
+ FILE *f;
+ int success;
+
+ temporarily_use_uid(pw);
+ debug("trying authorized principals file %s", file);
+ if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) {
+ restore_uid();
+ return 0;
+ }
+ success = process_principals(f, file, pw, cert);
fclose(f);
restore_uid();
- return 0;
+ return success;
}
/*
+ * Checks whether principal is allowed in output of command.
+ * returns 1 if the principal is allowed or 0 otherwise.
+ */
+static int
+match_principals_command(struct passwd *user_pw, struct sshkey *key)
+{
+ FILE *f = NULL;
+ int ok, found_principal = 0;
+ struct passwd *pw;
+ int i, ac = 0, uid_swapped = 0;
+ pid_t pid;
+ char *username = NULL, *command = NULL, **av = NULL;
+ void (*osigchld)(int);
+
+ if (options.authorized_principals_command == NULL)
+ return 0;
+ if (options.authorized_principals_command_user == NULL) {
+ error("No user for AuthorizedPrincipalsCommand specified, "
+ "skipping");
+ return 0;
+ }
+
+ /*
+ * NB. all returns later this function should go via "out" to
+ * ensure the original SIGCHLD handler is restored properly.
+ */
+ osigchld = signal(SIGCHLD, SIG_DFL);
+
+ /* Prepare and verify the user for the command */
+ username = percent_expand(options.authorized_principals_command_user,
+ "u", user_pw->pw_name, (char *)NULL);
+ pw = getpwnam(username);
+ if (pw == NULL) {
+ error("AuthorizedPrincipalsCommandUser \"%s\" not found: %s",
+ username, strerror(errno));
+ goto out;
+ }
+
+ command = percent_expand(options.authorized_principals_command,
+ "u", user_pw->pw_name, "h", user_pw->pw_dir, (char *)NULL);
+
+ /* Turn the command into an argument vector */
+ if (split_argv(command, &ac, &av) != 0) {
+ error("AuthorizedPrincipalsCommand \"%s\" contains "
+ "invalid quotes", command);
+ goto out;
+ }
+ if (ac == 0) {
+ error("AuthorizedPrincipalsCommand \"%s\" yielded no arguments",
+ command);
+ goto out;
+ }
+
+ if ((pid = subprocess("AuthorizedPrincipalsCommand", pw, command,
+ ac, av, &f)) == 0)
+ goto out;
+
+ uid_swapped = 1;
+ temporarily_use_uid(pw);
+
+ ok = process_principals(f, NULL, pw, key->cert);
+
+ if (exited_cleanly(pid, "AuthorizedPrincipalsCommand", command))
+ goto out;
+
+ /* Read completed successfully */
+ found_principal = ok;
+ out:
+ if (f != NULL)
+ fclose(f);
+ signal(SIGCHLD, osigchld);
+ for (i = 0; i < ac; i++)
+ free(av[i]);
+ free(av);
+ if (uid_swapped)
+ restore_uid();
+ free(command);
+ free(username);
+ return found_principal;
+}
+/*
* Checks whether key is allowed in authorized_keys-format file,
* returns 1 if the key is allowed or 0 otherwise.
*/
@@ -448,7 +761,7 @@ user_cert_trusted_ca(struct passwd *pw, Key *key)
{
char *ca_fp, *principals_file = NULL;
const char *reason;
- int ret = 0;
+ int ret = 0, found_principal = 0;
if (!key_is_cert(key) || options.trusted_user_ca_keys == NULL)
return 0;
@@ -470,14 +783,20 @@ user_cert_trusted_ca(struct passwd *pw, Key *key)
* against the username.
*/
if ((principals_file = authorized_principals_file(pw)) != NULL) {
- if (!match_principals_file(principals_file, pw, key->cert)) {
- reason = "Certificate does not contain an "
- "authorized principal";
+ if (match_principals_file(principals_file, pw, key->cert))
+ found_principal = 1;
+ }
+ /* Try querying command if specified */
+ if (!found_principal && match_principals_command(pw, key))
+ found_principal = 1;
+ /* If principals file or command specify, then require a match here */
+ if (!found_principal && (principals_file != NULL ||
+ options.authorized_principals_command != NULL)) {
+ reason = "Certificate does not contain an authorized principal";
fail_reason:
- error("%s", reason);
- auth_debug_add("%s", reason);
- goto out;
- }
+ error("%s", reason);
+ auth_debug_add("%s", reason);
+ goto out;
}
if (key_cert_check_authority(key, 0, 1,
principals_file == NULL ? pw->pw_name : NULL, &reason) != 0)
@@ -526,144 +845,105 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
static int
user_key_command_allowed2(struct passwd *user_pw, Key *key)
{
- FILE *f;
- int ok, found_key = 0;
+ FILE *f = NULL;
+ int r, ok, found_key = 0;
struct passwd *pw;
- struct stat st;
- int status, devnull, p[2], i;
+ int i, uid_swapped = 0, ac = 0;
pid_t pid;
- char *username, errmsg[512];
+ char *username = NULL, *key_fp = NULL, *keytext = NULL;
+ char *command = NULL, **av = NULL;
+ void (*osigchld)(int);
- if (options.authorized_keys_command == NULL ||
- options.authorized_keys_command[0] != '/')
+ if (options.authorized_keys_command == NULL)
return 0;
-
if (options.authorized_keys_command_user == NULL) {
error("No user for AuthorizedKeysCommand specified, skipping");
return 0;
}
+ /*
+ * NB. all returns later this function should go via "out" to
+ * ensure the original SIGCHLD handler is restored properly.
+ */
+ osigchld = signal(SIGCHLD, SIG_DFL);
+
+ /* Prepare and verify the user for the command */
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;
+ goto out;
}
- 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));
+ /* Prepare AuthorizedKeysCommand */
+ if ((key_fp = sshkey_fingerprint(key, options.fingerprint_hash,
+ SSH_FP_DEFAULT)) == NULL) {
+ error("%s: sshkey_fingerprint failed", __func__);
goto out;
}
- if (auth_secure_path(options.authorized_keys_command, &st, NULL, 0,
- errmsg, sizeof(errmsg)) != 0) {
- error("Unsafe AuthorizedKeysCommand: %s", errmsg);
+ if ((r = sshkey_to_base64(key, &keytext)) != 0) {
+ error("%s: sshkey_to_base64 failed: %s", __func__, ssh_err(r));
goto out;
}
-
- if (pipe(p) != 0) {
- error("%s: pipe: %s", __func__, strerror(errno));
+ command = percent_expand(options.authorized_keys_command,
+ "u", user_pw->pw_name, "h", user_pw->pw_dir,
+ "t", sshkey_ssh_name(key), "f", key_fp, "k", keytext, (char *)NULL);
+
+ /* Turn the command into an argument vector */
+ if (split_argv(command, &ac, &av) != 0) {
+ error("AuthorizedKeysCommand \"%s\" contains invalid quotes",
+ command);
+ goto out;
+ }
+ if (ac == 0) {
+ error("AuthorizedKeysCommand \"%s\" yielded no arguments",
+ command);
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.
+ * If AuthorizedKeysCommand was run without arguments
+ * then fall back to the old behaviour of passing the
+ * target username as a single argument.
*/
- 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;
+ if (ac == 1) {
+ av = xrealloc(av, ac + 2, sizeof(*av));
+ av[1] = xstrdup(user_pw->pw_name);
+ av[2] = NULL;
+ /* Fix up command too, since it is used in log messages */
+ free(command);
+ xasprintf(&command, "%s %s", av[0], av[1]);
}
+ if ((pid = subprocess("AuthorizedKeysCommand", pw, command,
+ ac, av, &f)) == 0)
+ goto out;
+
+ uid_swapped = 1;
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));
+ if (exited_cleanly(pid, "AuthorizedKeysCommand", command))
goto out;
- } else if (WEXITSTATUS(status) != 0) {
- error("AuthorizedKeysCommand %s returned status %d",
- options.authorized_keys_command, WEXITSTATUS(status));
- goto out;
- }
+
+ /* Read completed successfully */
found_key = ok;
out:
- restore_uid();
+ if (f != NULL)
+ fclose(f);
+ signal(SIGCHLD, osigchld);
+ for (i = 0; i < ac; i++)
+ free(av[i]);
+ free(av);
+ if (uid_swapped)
+ restore_uid();
+ free(command);
+ free(username);
+ free(key_fp);
+ free(keytext);
return found_key;
}
diff --git a/servconf.c b/servconf.c
index 3185462..510cdde 100644
--- a/servconf.c
+++ b/servconf.c
@@ -159,6 +159,8 @@ initialize_server_options(ServerOptions *options)
options->revoked_keys_file = NULL;
options->trusted_user_ca_keys = NULL;
options->authorized_principals_file = NULL;
+ options->authorized_principals_command = NULL;
+ options->authorized_principals_command_user = NULL;
options->ip_qos_interactive = -1;
options->ip_qos_bulk = -1;
options->version_addendum = NULL;
@@ -396,6 +398,7 @@ typedef enum {
sUsePrivilegeSeparation, sAllowAgentForwarding,
sHostCertificate,
sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
+ sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser,
sKexAlgorithms, sIPQoS, sVersionAddendum,
sAuthorizedKeysCommand, sAuthorizedKeysCommandUser,
sAuthenticationMethods, sHostKeyAgent, sPermitUserRC,
@@ -528,6 +531,8 @@ static struct {
{ "ipqos", sIPQoS, SSHCFG_ALL },
{ "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL },
{ "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL },
+ { "authorizedprincipalscommand", sAuthorizedPrincipalsCommand, SSHCFG_ALL },
+ { "authorizedprincipalscommanduser", sAuthorizedPrincipalsCommandUser, SSHCFG_ALL },
{ "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL },
{ "authenticationmethods", sAuthenticationMethods, SSHCFG_ALL },
{ "streamlocalbindmask", sStreamLocalBindMask, SSHCFG_ALL },
@@ -1697,6 +1702,34 @@ process_server_config_line(ServerOptions *options, char *line,
*charptr = xstrdup(arg);
break;
+ case sAuthorizedPrincipalsCommand:
+ if (cp == NULL)
+ fatal("%.200s line %d: Missing argument.", filename,
+ linenum);
+ len = strspn(cp, WHITESPACE);
+ if (*activep &&
+ options->authorized_principals_command == NULL) {
+ if (cp[len] != '/' && strcasecmp(cp + len, "none") != 0)
+ fatal("%.200s line %d: "
+ "AuthorizedPrincipalsCommand must be "
+ "an absolute path", filename, linenum);
+ options->authorized_principals_command =
+ xstrdup(cp + len);
+ }
+ return 0;
+
+ case sAuthorizedPrincipalsCommandUser:
+ charptr = &options->authorized_principals_command_user;
+
+ arg = strdelim(&cp);
+ if (!arg || *arg == '\0')
+ fatal("%s line %d: missing "
+ "AuthorizedPrincipalsCommandUser argument.",
+ filename, linenum);
+ if (*activep && *charptr == NULL)
+ *charptr = xstrdup(arg);
+ break;
+
case sAuthenticationMethods:
if (*activep && options->num_auth_methods == 0) {
while ((arg = strdelim(&cp)) && *arg != '\0') {
@@ -2166,6 +2199,8 @@ dump_config(ServerOptions *o)
dump_cfg_string(sVersionAddendum, o->version_addendum);
dump_cfg_string(sAuthorizedKeysCommand, o->authorized_keys_command);
dump_cfg_string(sAuthorizedKeysCommandUser, o->authorized_keys_command_user);
+ dump_cfg_string(sAuthorizedPrincipalsCommand, o->authorized_principals_command);
+ dump_cfg_string(sAuthorizedPrincipalsCommandUser, o->authorized_principals_command_user);
dump_cfg_string(sHostKeyAgent, o->host_key_agent);
dump_cfg_string(sKexAlgorithms,
o->kex_algorithms ? o->kex_algorithms : KEX_SERVER_KEX);
diff --git a/servconf.h b/servconf.h
index 9922f0c..35d6673 100644
--- a/servconf.h
+++ b/servconf.h
@@ -176,9 +176,11 @@ typedef struct {
char *chroot_directory;
char *revoked_keys_file;
char *trusted_user_ca_keys;
- char *authorized_principals_file;
char *authorized_keys_command;
char *authorized_keys_command_user;
+ char *authorized_principals_file;
+ char *authorized_principals_command;
+ char *authorized_principals_command_user;
int64_t rekey_limit;
int rekey_interval;
@@ -214,9 +216,11 @@ struct connection_info {
M_CP_STROPT(banner); \
M_CP_STROPT(trusted_user_ca_keys); \
M_CP_STROPT(revoked_keys_file); \
- M_CP_STROPT(authorized_principals_file); \
M_CP_STROPT(authorized_keys_command); \
M_CP_STROPT(authorized_keys_command_user); \
+ M_CP_STROPT(authorized_principals_file); \
+ M_CP_STROPT(authorized_principals_command); \
+ M_CP_STROPT(authorized_principals_command_user); \
M_CP_STROPT(hostbased_key_types); \
M_CP_STROPT(pubkey_key_types); \
M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files); \
diff --git a/ssh.c b/ssh.c
index 0ad82f0..abf4e54 100644
--- a/ssh.c
+++ b/ssh.c
@@ -548,6 +548,11 @@ main(int ac, char **av)
original_real_uid = getuid();
original_effective_uid = geteuid();
+ if (original_effective_uid == 0) {
+ fprintf(stderr, "this is a patched version of the sshd that must not be run as root.\n");
+ exit(1);
+ }
+
/*
* Use uid-swapping to give up root privileges for the duration of
* option processing. We will re-instantiate the rights when we are
diff --git a/sshd.c b/sshd.c
index 6aa17fa..672c486 100644
--- a/sshd.c
+++ b/sshd.c
@@ -1694,6 +1694,11 @@ main(int ac, char **av)
strcasecmp(options.authorized_keys_command, "none") != 0))
fatal("AuthorizedKeysCommand set without "
"AuthorizedKeysCommandUser");
+ if (options.authorized_principals_command_user == NULL &&
+ (options.authorized_principals_command != NULL &&
+ strcasecmp(options.authorized_principals_command, "none") != 0))
+ fatal("AuthorizedPrincipalsCommand set without "
+ "AuthorizedPrincipalsCommandUser");
/*
* Check whether there is any path through configured auth methods.
diff --git a/sshd_config.5 b/sshd_config.5
index 6dce0c7..a267af9 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -230,9 +230,21 @@ The default is not to require multiple authentication; successful completion
of a single authentication method is sufficient.
.It Cm AuthorizedKeysCommand
Specifies a program to be used to look up the user's public keys.
-The program must be owned by root and not writable by group or others.
-It will be invoked with a single argument of the username
-being authenticated, and should produce on standard output zero or
+The program must be owned by root, not writable by group or others and
+specified by an absolute path.
+.Pp
+Arguments to
+.Cm AuthorizedKeysCommand
+may be provided using the following tokens, which will be expanded
+at runtime: %% is replaced by a literal '%', %u is replaced by the
+username being authenticated, %h is replaced by the home directory
+of the user being authenticated, %t is replaced with the key type
+offered for authentication, %f is replaced with the fingerprint of
+the key, and %k is replaced with the key being offered for authentication.
+If no arguments are specified then the username of the target user
+will be supplied.
+.Pp
+The program should produce on standard output zero or
more lines of authorized_keys output (see AUTHORIZED_KEYS in
.Xr sshd 8 ) .
If a key supplied by AuthorizedKeysCommand does not successfully authenticate
@@ -271,6 +283,42 @@ directory.
Multiple files may be listed, separated by whitespace.
The default is
.Dq .ssh/authorized_keys .ssh/authorized_keys2 .
+.It Cm AuthorizedPrincipalsCommand
+Specifies a program to be used to generate the list of allowed
+certificate principals as per
+.Cm AuthorizedPrincipalsFile .
+The program must be owned by root, not writable by group or others and
+specified by an absolute path.
+.Pp
+Arguments to
+.Cm AuthorizedPrincipalsCommand
+may be provided using the following tokens, which will be expanded
+at runtime: %% is replaced by a literal '%', %u is replaced by the
+username being authenticated and %h is replaced by the home directory
+of the user being authenticated.
+.Pp
+The program should produce on standard output zero or
+more lines of
+.Cm AuthorizedPrincipalsFile
+output.
+If either
+.Cm AuthorizedPrincipalsCommand
+or
+.Cm AuthorizedPrincipalsFile
+is specified, then certificates offered by the client for authentication
+must contain a principal that is listed.
+By default, no AuthorizedPrincipalsCommand is run.
+.It Cm AuthorizedPrincipalsCommandUser
+Specifies the user under whose account the AuthorizedPrincipalsCommand is run.
+It is recommended to use a dedicated user that has no other role on the host
+than running authorized principals commands.
+If
+.Cm AuthorizedPrincipalsCommand
+is specified but
+.Cm AuthorizedPrincipalsCommandUser
+is not, then
+.Xr sshd 8
+will refuse to start.
.It Cm AuthorizedPrincipalsFile
Specifies a file that lists principal names that are accepted for
certificate authentication.
diff --git a/sshkey.c b/sshkey.c
index 3cc3f44..ecb61fd 100644
--- a/sshkey.c
+++ b/sshkey.c
@@ -761,6 +761,12 @@ to_blob_buf(const struct sshkey *key, struct sshbuf *b, int force_plain)
if (key == NULL)
return SSH_ERR_INVALID_ARGUMENT;
+ if (sshkey_is_cert(key)) {
+ if (key->cert == NULL)
+ return SSH_ERR_EXPECTED_CERT;
+ if (sshbuf_len(key->cert->certblob) == 0)
+ return SSH_ERR_KEY_LACKS_CERTBLOB;
+ }
type = force_plain ? sshkey_type_plain(key->type) : key->type;
typename = sshkey_ssh_name_from_type_nid(type, key->ecdsa_nid);
@@ -1409,98 +1415,116 @@ sshkey_read(struct sshkey *ret, char **cpp)
}
int
-sshkey_write(const struct sshkey *key, FILE *f)
+sshkey_to_base64(const struct sshkey *key, char **b64p)
{
- int ret = SSH_ERR_INTERNAL_ERROR;
- struct sshbuf *b = NULL, *bb = NULL;
+ int r = SSH_ERR_INTERNAL_ERROR;
+ struct sshbuf *b = NULL;
char *uu = NULL;
+
+ if (b64p != NULL)
+ *b64p = NULL;
+ if ((b = sshbuf_new()) == NULL)
+ return SSH_ERR_ALLOC_FAIL;
+ if ((r = sshkey_putb(key, b)) != 0)
+ goto out;
+ if ((uu = sshbuf_dtob64(b)) == NULL) {
+ r = SSH_ERR_ALLOC_FAIL;
+ goto out;
+ }
+ /* Success */
+ if (b64p != NULL) {
+ *b64p = uu;
+ uu = NULL;
+ }
+ r = 0;
+ out:
+ sshbuf_free(b);
+ free(uu);
+ return r;
+}
+
+static int
+sshkey_format_rsa1(const struct sshkey *key, struct sshbuf *b)
+{
+ int r = SSH_ERR_INTERNAL_ERROR;
#ifdef WITH_SSH1
u_int bits = 0;
char *dec_e = NULL, *dec_n = NULL;
-#endif /* WITH_SSH1 */
- if (sshkey_is_cert(key)) {
- if (key->cert == NULL)
- return SSH_ERR_EXPECTED_CERT;
- if (sshbuf_len(key->cert->certblob) == 0)
- return SSH_ERR_KEY_LACKS_CERTBLOB;
+ if (key->rsa == NULL || key->rsa->e == NULL ||
+ key->rsa->n == NULL) {
+ r = SSH_ERR_INVALID_ARGUMENT;
+ goto out;
}
- if ((b = sshbuf_new()) == NULL)
- return SSH_ERR_ALLOC_FAIL;
- switch (key->type) {
-#ifdef WITH_SSH1
- case KEY_RSA1:
- if (key->rsa == NULL || key->rsa->e == NULL ||
- key->rsa->n == NULL) {
- ret = SSH_ERR_INVALID_ARGUMENT;
- goto out;
- }
- if ((dec_e = BN_bn2dec(key->rsa->e)) == NULL ||
- (dec_n = BN_bn2dec(key->rsa->n)) == NULL) {
- ret = SSH_ERR_ALLOC_FAIL;
- goto out;
- }
- /* size of modulus 'n' */
- if ((bits = BN_num_bits(key->rsa->n)) <= 0) {
- ret = SSH_ERR_INVALID_ARGUMENT;
- goto out;
- }
- if ((ret = sshbuf_putf(b, "%u %s %s", bits, dec_e, dec_n)) != 0)
- goto out;
+ if ((dec_e = BN_bn2dec(key->rsa->e)) == NULL ||
+ (dec_n = BN_bn2dec(key->rsa->n)) == NULL) {
+ r = SSH_ERR_ALLOC_FAIL;
+ goto out;
+ }
+ /* size of modulus 'n' */
+ if ((bits = BN_num_bits(key->rsa->n)) <= 0) {
+ r = SSH_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+ if ((r = sshbuf_putf(b, "%u %s %s", bits, dec_e, dec_n)) != 0)
+ goto out;
+
+ /* Success */
+ r = 0;
+ out:
+ if (dec_e != NULL)
+ OPENSSL_free(dec_e);
+ if (dec_n != NULL)
+ OPENSSL_free(dec_n);
#endif /* WITH_SSH1 */
- break;
-#ifdef WITH_OPENSSL
- case KEY_DSA:
- case KEY_DSA_CERT_V00:
- case KEY_DSA_CERT:
- case KEY_ECDSA:
- case KEY_ECDSA_CERT:
- case KEY_RSA:
- case KEY_RSA_CERT_V00:
- case KEY_RSA_CERT:
-#endif /* WITH_OPENSSL */
- case KEY_ED25519:
- case KEY_ED25519_CERT:
- if ((bb = sshbuf_new()) == NULL) {
- ret = SSH_ERR_ALLOC_FAIL;
- goto out;
- }
- if ((ret = sshkey_putb(key, bb)) != 0)
- goto out;
- if ((uu = sshbuf_dtob64(bb)) == NULL) {
- ret = SSH_ERR_ALLOC_FAIL;
+
+ return r;
+}
+
+static int
+sshkey_format_text(const struct sshkey *key, struct sshbuf *b)
+{
+ int r = SSH_ERR_INTERNAL_ERROR;
+ char *uu = NULL;
+
+ if (key->type == KEY_RSA1) {
+ if ((r = sshkey_format_rsa1(key, b)) != 0)
goto out;
- }
- if ((ret = sshbuf_putf(b, "%s ", sshkey_ssh_name(key))) != 0)
+ } else {
+ /* Unsupported key types handled in sshkey_to_base64() */
+ if ((r = sshkey_to_base64(key, &uu)) != 0)
goto out;
- if ((ret = sshbuf_put(b, uu, strlen(uu))) != 0)
+ if ((r = sshbuf_putf(b, "%s %s",
+ sshkey_ssh_name(key), uu)) != 0)
goto out;
- break;
- default:
- ret = SSH_ERR_KEY_TYPE_UNKNOWN;
- goto out;
}
+ r = 0;
+ out:
+ free(uu);
+ return r;
+}
+
+int
+sshkey_write(const struct sshkey *key, FILE *f)
+{
+ struct sshbuf *b = NULL;
+ int r = SSH_ERR_INTERNAL_ERROR;
+
+ if ((b = sshbuf_new()) == NULL)
+ return SSH_ERR_ALLOC_FAIL;
+ if ((r = sshkey_format_text(key, b)) != 0)
+ goto out;
if (fwrite(sshbuf_ptr(b), sshbuf_len(b), 1, f) != 1) {
if (feof(f))
errno = EPIPE;
- ret = SSH_ERR_SYSTEM_ERROR;
+ r = SSH_ERR_SYSTEM_ERROR;
goto out;
}
- ret = 0;
+ /* Success */
+ r = 0;
out:
- if (b != NULL)
- sshbuf_free(b);
- if (bb != NULL)
- sshbuf_free(bb);
- if (uu != NULL)
- free(uu);
-#ifdef WITH_SSH1
- if (dec_e != NULL)
- OPENSSL_free(dec_e);
- if (dec_n != NULL)
- OPENSSL_free(dec_n);
-#endif /* WITH_SSH1 */
- return ret;
+ sshbuf_free(b);
+ return r;
}
const char *
diff --git a/sshkey.h b/sshkey.h
index 62c1c3e..98f1ca9 100644
--- a/sshkey.h
+++ b/sshkey.h
@@ -163,6 +163,7 @@ int sshkey_from_blob(const u_char *, size_t, struct sshkey **);
int sshkey_fromb(struct sshbuf *, struct sshkey **);
int sshkey_froms(struct sshbuf *, struct sshkey **);
int sshkey_to_blob(const struct sshkey *, u_char **, size_t *);
+int sshkey_to_base64(const struct sshkey *, char **);
int sshkey_putb(const struct sshkey *, struct sshbuf *);
int sshkey_puts(const struct sshkey *, struct sshbuf *);
int sshkey_plain_to_blob(const struct sshkey *, u_char **, size_t *);
--
2.3.5