Add a StdinNull directive to ssh_config(5) that allows the config
authordjm <djm@openbsd.org>
Fri, 23 Jul 2021 04:00:59 +0000 (04:00 +0000)
committerdjm <djm@openbsd.org>
Fri, 23 Jul 2021 04:00:59 +0000 (04:00 +0000)
file to do the same thing as -n does on the ssh(1) commandline.
Patch from Volker Diels-Grabsch via GHPR231; ok dtucker

usr.bin/ssh/clientloop.c
usr.bin/ssh/mux.c
usr.bin/ssh/readconf.c
usr.bin/ssh/readconf.h
usr.bin/ssh/ssh.1
usr.bin/ssh/ssh.c
usr.bin/ssh/ssh_config.5
usr.bin/ssh/sshsig.c

index b84c986..1e1fb8e 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.367 2021/07/16 09:00:23 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.368 2021/07/23 04:00:59 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
 /* import options */
 extern Options options;
 
-/* Flag indicating that stdin should be redirected from /dev/null. */
-extern int stdin_null_flag;
-
 /* Flag indicating that ssh should daemonise after authentication is complete */
 extern int fork_after_authentication_flag;
 
index 78dd4fe..e39a134 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: mux.c,v 1.90 2021/07/13 23:48:36 djm Exp $ */
+/* $OpenBSD: mux.c,v 1.91 2021/07/23 04:00:59 djm Exp $ */
 /*
  * Copyright (c) 2002-2008 Damien Miller <djm@openbsd.org>
  *
@@ -58,7 +58,6 @@
 /* from ssh.c */
 extern int tty_flag;
 extern Options options;
-extern int stdin_null_flag;
 extern char *host;
 extern struct sshbuf *command;
 extern volatile sig_atomic_t quit_pending;
@@ -1860,7 +1859,7 @@ mux_client_request_session(int fd)
 
        ssh_signal(SIGPIPE, SIG_IGN);
 
-       if (stdin_null_flag && stdfd_devnull(1, 0, 0) == -1)
+       if (options.stdin_null && stdfd_devnull(1, 0, 0) == -1)
                fatal_f("stdfd_devnull failed");
 
        if ((term = lookup_env_in_list("TERM", options.setenv,
@@ -2082,7 +2081,7 @@ mux_client_request_stdio_fwd(int fd)
 
        ssh_signal(SIGPIPE, SIG_IGN);
 
-       if (stdin_null_flag && stdfd_devnull(1, 0, 0) == -1)
+       if (options.stdin_null && stdfd_devnull(1, 0, 0) == -1)
                fatal_f("stdfd_devnull failed");
 
        if ((m = sshbuf_new()) == NULL)
index 761ca67..316ed5a 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.359 2021/07/13 23:48:36 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.360 2021/07/23 04:00:59 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -153,7 +153,7 @@ typedef enum {
        oTunnel, oTunnelDevice,
        oLocalCommand, oPermitLocalCommand, oRemoteCommand,
        oVisualHostKey,
-       oKexAlgorithms, oIPQoS, oRequestTTY, oSessionType,
+       oKexAlgorithms, oIPQoS, oRequestTTY, oSessionType, oStdinNull,
        oIgnoreUnknown, oProxyUseFdpass,
        oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots,
        oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
@@ -285,6 +285,7 @@ static struct {
        { "ipqos", oIPQoS },
        { "requesttty", oRequestTTY },
        { "sessiontype", oSessionType },
+       { "stdinnull", oStdinNull },
        { "proxyusefdpass", oProxyUseFdpass },
        { "canonicaldomains", oCanonicalDomains },
        { "canonicalizefallbacklocal", oCanonicalizeFallbackLocal },
@@ -1940,6 +1941,10 @@ parse_pubkey_algos:
                multistate_ptr = multistate_sessiontype;
                goto parse_multistate;
 
+       case oStdinNull:
+               intptr = &options->stdin_null;
+               goto parse_flag;
+
        case oIgnoreUnknown:
                charptr = &options->ignored_unknown;
                goto parse_string;
@@ -2363,6 +2368,7 @@ initialize_options(Options * options)
        options->ip_qos_bulk = -1;
        options->request_tty = -1;
        options->session_type = -1;
+       options->stdin_null = -1;
        options->proxy_use_fdpass = -1;
        options->ignored_unknown = NULL;
        options->num_canonical_domains = 0;
@@ -2549,6 +2555,8 @@ fill_default_options(Options * options)
                options->request_tty = REQUEST_TTY_AUTO;
        if (options->session_type == -1)
                options->session_type = SESSION_TYPE_DEFAULT;
+       if (options->stdin_null == -1)
+               options->stdin_null = 0;
        if (options->proxy_use_fdpass == -1)
                options->proxy_use_fdpass = 0;
        if (options->canonicalize_max_dots == -1)
@@ -3222,6 +3230,7 @@ dump_client_config(Options *o, const char *host)
        dump_cfg_fmtint(oPubkeyAuthentication, o->pubkey_authentication);
        dump_cfg_fmtint(oRequestTTY, o->request_tty);
        dump_cfg_fmtint(oSessionType, o->session_type);
+       dump_cfg_fmtint(oStdinNull, o->stdin_null);
        dump_cfg_fmtint(oStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink);
        dump_cfg_fmtint(oStrictHostKeyChecking, o->strict_host_key_checking);
        dump_cfg_fmtint(oTCPKeepAlive, o->tcp_keep_alive);
index e4ebc6f..08ca9e7 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.142 2021/07/13 23:48:36 djm Exp $ */
+/* $OpenBSD: readconf.h,v 1.143 2021/07/23 04:00:59 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -147,6 +147,7 @@ typedef struct {
 
        int     request_tty;
        int     session_type;
+       int     stdin_null;
 
        int     proxy_use_fdpass;
 
index 6d08397..b31175f 100644 (file)
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh.1,v 1.422 2021/07/13 23:48:36 djm Exp $
-.Dd $Mdocdate: July 13 2021 $
+.\" $OpenBSD: ssh.1,v 1.423 2021/07/23 04:00:59 djm Exp $
+.Dd $Mdocdate: July 23 2021 $
 .Dt SSH 1
 .Os
 .Sh NAME
@@ -451,6 +451,11 @@ program will be put in the background.
 needs to ask for a password or passphrase; see also the
 .Fl f
 option.)
+Refer to the description of
+.Cm StdinNull
+in
+.Xr ssh_config 5
+for details.
 .Pp
 .It Fl O Ar ctl_cmd
 Control an active connection multiplexing master process.
@@ -553,6 +558,7 @@ For full details of the options listed below, and their possible values, see
 .It ServerAliveCountMax
 .It SessionType
 .It SetEnv
+.It StdinNull
 .It StreamLocalBindMask
 .It StreamLocalBindUnlink
 .It StrictHostKeyChecking
index 66ce7b1..0b86ab8 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh.c,v 1.562 2021/07/17 00:38:11 djm Exp $ */
+/* $OpenBSD: ssh.c,v 1.563 2021/07/23 04:00:59 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -110,12 +110,6 @@ int debug_flag = 0;
 /* Flag indicating whether a tty should be requested */
 int tty_flag = 0;
 
-/*
- * Flag indicating that nothing should be read from stdin.  This can be set
- * on the command line.
- */
-int stdin_null_flag = 0;
-
 /*
  * Flag indicating that the current process should be backgrounded and
  * a new mux-client launched in the foreground for ControlPersist.
@@ -697,11 +691,11 @@ main(int ac, char **av)
                        options.address_family = AF_INET6;
                        break;
                case 'n':
-                       stdin_null_flag = 1;
+                       options.stdin_null = 1;
                        break;
                case 'f':
                        fork_after_authentication_flag = 1;
-                       stdin_null_flag = 1;
+                       options.stdin_null = 1;
                        break;
                case 'x':
                        options.forward_x11 = 0;
@@ -1336,7 +1330,7 @@ main(int ac, char **av)
            (muxclient_command && muxclient_command != SSHMUX_COMMAND_PROXY))
                tty_flag = 0;
        /* Do not allocate a tty if stdin is not a tty. */
-       if ((!isatty(fileno(stdin)) || stdin_null_flag) &&
+       if ((!isatty(fileno(stdin)) || options.stdin_null) &&
            options.request_tty != REQUEST_TTY_FORCE) {
                if (tty_flag)
                        logit("Pseudo-terminal will not be allocated because "
@@ -1713,7 +1707,7 @@ control_persist_detach(void)
        default:
                /* Parent: set up mux client to connect to backgrounded master */
                debug2_f("background process is %ld", (long)pid);
-               stdin_null_flag = ostdin_null_flag;
+               options.stdin_null = ostdin_null_flag;
                options.request_tty = orequest_tty;
                tty_flag = otty_flag;
                options.session_type = osession_type;
@@ -2054,7 +2048,7 @@ ssh_session2_open(struct ssh *ssh)
        Channel *c;
        int window, packetmax, in, out, err;
 
-       if (stdin_null_flag) {
+       if (options.stdin_null) {
                in = open(_PATH_DEVNULL, O_RDONLY);
        } else {
                in = dup(STDIN_FILENO);
@@ -2123,11 +2117,11 @@ ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
         * async rfwd replies have been received for ExitOnForwardFailure).
         */
        if (options.control_persist && muxserver_sock != -1) {
-               ostdin_null_flag = stdin_null_flag;
+               ostdin_null_flag = options.stdin_null;
                osession_type = options.session_type;
                orequest_tty = options.request_tty;
                otty_flag = tty_flag;
-               stdin_null_flag = 1;
+               options.stdin_null = 1;
                options.session_type = SESSION_TYPE_NONE;
                tty_flag = 0;
                if (!fork_after_authentication_flag &&
index 793035b..3464bb0 100644 (file)
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh_config.5,v 1.357 2021/07/14 06:46:38 jmc Exp $
-.Dd $Mdocdate: July 14 2021 $
+.\" $OpenBSD: ssh_config.5,v 1.358 2021/07/23 04:00:59 djm Exp $
+.Dd $Mdocdate: July 23 2021 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -1676,6 +1676,22 @@ Similarly to
 with the exception of the
 .Ev TERM
 variable, the server must be prepared to accept the environment variable.
+.It Cm StdinNull
+Redirects stdin from
+.Pa /dev/null
+(actually, prevents reading from stdin).
+Either this or the equivalent
+.Fl n
+option must be used when
+.Nm ssh
+is run in the background.
+The argument to this keyword must be
+.Cm yes
+(same as the
+.Fl n
+option) or
+.Cm no
+(the default).
 .It Cm StreamLocalBindMask
 Sets the octal file creation mode mask
 .Pq umask
index 58ad4c2..9301f2e 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshsig.c,v 1.20 2021/01/31 10:50:10 dtucker Exp $ */
+/* $OpenBSD: sshsig.c,v 1.21 2021/07/23 04:00:59 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -614,6 +614,7 @@ sshsig_verify_fd(struct sshbuf *signature, int fd,
 struct sshsigopt {
        int ca;
        char *namespaces;
+       uint64_t valid_after, valid_before;
 };
 
 struct sshsigopt *
@@ -622,6 +623,7 @@ sshsigopt_parse(const char *opts, const char *path, u_long linenum,
 {
        struct sshsigopt *ret;
        int r;
+       char *opt;
        const char *errstr = NULL;
 
        if ((ret = calloc(1, sizeof(*ret))) == NULL)
@@ -641,6 +643,34 @@ sshsigopt_parse(const char *opts, const char *path, u_long linenum,
                        ret->namespaces = opt_dequote(&opts, &errstr);
                        if (ret->namespaces == NULL)
                                goto fail;
+               } else if (opt_match(&opts, "valid-after")) {
+                       if (ret->valid_after != 0) {
+                               errstr = "multiple \"valid-after\" clauses";
+                               goto fail;
+                       }
+                       if ((opt = opt_dequote(&opts, &errstr)) == NULL)
+                               goto fail;
+                       if (parse_absolute_time(opt, &ret->valid_after) != 0 ||
+                           ret->valid_after == 0) {
+                               free(opt);
+                               errstr = "invalid \"valid-after\" time";
+                               goto fail;
+                       }
+                       free(opt);
+               } else if (opt_match(&opts, "valid-before")) {
+                       if (ret->valid_before != 0) {
+                               errstr = "multiple \"valid-before\" clauses";
+                               goto fail;
+                       }
+                       if ((opt = opt_dequote(&opts, &errstr)) == NULL)
+                               goto fail;
+                       if (parse_absolute_time(opt, &ret->valid_before) != 0 ||
+                           ret->valid_before == 0) {
+                               free(opt);
+                               errstr = "invalid \"valid-before\" time";
+                               goto fail;
+                       }
+                       free(opt);
                }
                /*
                 * Skip the comma, and move to the next option
@@ -659,6 +689,12 @@ sshsigopt_parse(const char *opts, const char *path, u_long linenum,
                        goto fail;
                }
        }
+       /* final consistency check */
+       if (ret->valid_after != 0 && ret->valid_before != 0 &&
+           ret->valid_before <= ret->valid_after) {
+               errstr = "\"valid-before\" time is before \"valid-after\"";
+               goto fail;
+       }
        /* success */
        return ret;
  fail:
@@ -777,12 +813,13 @@ parse_principals_key_and_options(const char *path, u_long linenum, char *line,
 static int
 check_allowed_keys_line(const char *path, u_long linenum, char *line,
     const struct sshkey *sign_key, const char *principal,
-    const char *sig_namespace)
+    const char *sig_namespace, uint64_t verify_time)
 {
        struct sshkey *found_key = NULL;
-       int r, found = 0;
+       int r, success = 0;
        const char *reason = NULL;
        struct sshsigopt *sigopts = NULL;
+       char tvalid[64], tverify[64];
 
        /* Parse the line */
        if ((r = parse_principals_key_and_options(path, linenum, line,
@@ -791,44 +828,63 @@ check_allowed_keys_line(const char *path, u_long linenum, char *line,
                goto done;
        }
 
-       /* Check whether options preclude the use of this key */
-       if (sigopts->namespaces != NULL &&
-           match_pattern_list(sig_namespace, sigopts->namespaces, 0) != 1) {
-               error("%s:%lu: key is not permitted for use in signature "
-                   "namespace \"%s\"", path, linenum, sig_namespace);
-               goto done;
-       }
-
        if (!sigopts->ca && sshkey_equal(found_key, sign_key)) {
                /* Exact match of key */
-               debug("%s:%lu: matched key and principal", path, linenum);
-               /* success */
-               found = 1;
+               debug("%s:%lu: matched key", path, linenum);
        } else if (sigopts->ca && sshkey_is_cert(sign_key) &&
            sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
                /* Match of certificate's CA key */
                if ((r = sshkey_cert_check_authority(sign_key, 0, 1, 0,
-                   principal, &reason)) != 0) {
+                   verify_time, principal, &reason)) != 0) {
                        error("%s:%lu: certificate not authorized: %s",
                            path, linenum, reason);
                        goto done;
                }
                debug("%s:%lu: matched certificate CA key", path, linenum);
-               /* success */
-               found = 1;
        } else {
-               /* Principal matched but key didn't */
+               /* Didn't match key */
+               goto done;
+       }
+
+       /* Check whether options preclude the use of this key */
+       if (sigopts->namespaces != NULL &&
+           match_pattern_list(sig_namespace, sigopts->namespaces, 0) != 1) {
+               error("%s:%lu: key is not permitted for use in signature "
+                   "namespace \"%s\"", path, linenum, sig_namespace);
+               goto done;
+       }
+
+       /* check key time validity */
+       format_absolute_time((uint64_t)verify_time, tverify, sizeof(tverify));
+       if (sigopts->valid_after != 0 &&
+           (uint64_t)verify_time < sigopts->valid_after) {
+               format_absolute_time(sigopts->valid_after,
+                   tvalid, sizeof(tvalid));
+               error("%s:%lu: key is not yet valid: "
+                   "verify time %s < valid-after %s", path, linenum,
+                   tverify, tvalid);
                goto done;
        }
+       if (sigopts->valid_before != 0 &&
+           (uint64_t)verify_time > sigopts->valid_before) {
+               format_absolute_time(sigopts->valid_before,
+                   tvalid, sizeof(tvalid));
+               error("%s:%lu: key has expired: "
+                   "verify time %s > valid-before %s", path, linenum,
+                   tverify, tvalid);
+               goto done;
+       }
+       success = 1;
+
  done:
        sshkey_free(found_key);
        sshsigopt_free(sigopts);
-       return found ? 0 : SSH_ERR_KEY_NOT_FOUND;
+       return success ? 0 : SSH_ERR_KEY_NOT_FOUND;
 }
 
 int
 sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
-    const char *principal, const char *sig_namespace)
+    const char *principal, const char *sig_namespace, uint64_t verify_time)
 {
        FILE *f = NULL;
        char *line = NULL;
@@ -848,7 +904,7 @@ sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
        while (getline(&line, &linesize, f) != -1) {
                linenum++;
                r = check_allowed_keys_line(path, linenum, line, sign_key,
-                   principal, sig_namespace);
+                   principal, sig_namespace, verify_time);
                free(line);
                line = NULL;
                linesize = 0;
@@ -869,7 +925,7 @@ sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
 
 static int
 cert_filter_principals(const char *path, u_long linenum,
-    char **principalsp, const struct sshkey *cert)
+    char **principalsp, const struct sshkey *cert, uint64_t verify_time)
 {
        char *cp, *oprincipals, *principals;
        const char *reason;
@@ -892,7 +948,7 @@ cert_filter_principals(const char *path, u_long linenum,
                }
                /* Check against principals list in certificate */
                if ((r = sshkey_cert_check_authority(cert, 0, 1, 0,
-                   cp, &reason)) != 0) {
+                   verify_time, cp, &reason)) != 0) {
                        debug("%s:%lu: principal \"%s\" not authorized: %s",
                            path, linenum, cp, reason);
                        continue;
@@ -923,7 +979,7 @@ cert_filter_principals(const char *path, u_long linenum,
 
 static int
 get_matching_principals_from_line(const char *path, u_long linenum, char *line,
-    const struct sshkey *sign_key, char **principalsp)
+    const struct sshkey *sign_key, uint64_t verify_time, char **principalsp)
 {
        struct sshkey *found_key = NULL;
        char *principals = NULL;
@@ -949,7 +1005,7 @@ get_matching_principals_from_line(const char *path, u_long linenum, char *line,
            sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
                /* Remove principals listed in file but not allowed by cert */
                if ((r = cert_filter_principals(path, linenum,
-                   &principals, sign_key)) != 0) {
+                   &principals, sign_key, verify_time)) != 0) {
                        /* error already displayed */
                        debug_r(r, "%s:%lu: cert_filter_principals",
                            path, linenum);
@@ -975,7 +1031,7 @@ get_matching_principals_from_line(const char *path, u_long linenum, char *line,
 
 int
 sshsig_find_principals(const char *path, const struct sshkey *sign_key,
-    char **principals)
+    uint64_t verify_time, char **principals)
 {
        FILE *f = NULL;
        char *line = NULL;
@@ -994,7 +1050,7 @@ sshsig_find_principals(const char *path, const struct sshkey *sign_key,
        while (getline(&line, &linesize, f) != -1) {
                linenum++;
                r = get_matching_principals_from_line(path, linenum, line,
-                   sign_key, principals);
+                   sign_key, verify_time, principals);
                free(line);
                line = NULL;
                linesize = 0;