Index: Makefile =================================================================== RCS file: /cvs/src/usr.bin/ssh/Makefile,v retrieving revision 1.10 diff -u -u -r1.10 Makefile --- Makefile 9 Feb 2002 17:37:34 -0000 1.10 +++ Makefile 14 Feb 2002 11:24:09 -0000 @@ -10,5 +10,7 @@ ${DESTDIR}/etc/ssh/ssh_config install -C -o root -g wheel -m 0644 ${.CURDIR}/sshd_config \ ${DESTDIR}/etc/ssh/sshd_config + install -C -o root -g wheel -m 0600 ${.CURDIR}/sshd_policy \ + ${DESTDIR}/etc/ssh/sshd_policy .include Index: README.keynote =================================================================== RCS file: README.keynote diff -N README.keynote --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ README.keynote 14 Feb 2002 11:24:10 -0000 @@ -0,0 +1,159 @@ +Keynote in OpenSSH +------------------ + +To enable Keynote policy support, build OpenSSH using "make KEYNOTE=yes" + +System policies go in /etc/sshd_policy and are always read. User policies +are conditional on server config directive "LoadUserPolicies" and live in +~/.ssh/policy. Both policies are loaded with ASSERT_FLAG_LOCAL set. + +XXX Later we can do signed policies sent over the wire. + +Global action variables + + app_domain = "SSH" + user + user_id + group_id + shell + remote_ip + remote_port + time_of_day (encoded as HHMMSS) + day_of_week (Sunday = 0) + date (encoded as YYYYMMDD) + protocol ("1" or "2") + client_version + + Protocol 2 specific: + kex_type (key exchange type) + cipher2_ctos (cipher used client->server for ssh2) + mac2_ctos + comp2_ctos + cipher2_stoc + mac2_stoc + comp2_stoc + + Protocol 1 specific: + cipher1 + +XXX Perhaps: + + dhgex_bits + system_load + no_login (set if /etc/nologin exists) + +Specific access control checks and local action variables: + +Authentication - + + return values: true, false + +Protocol 1: + + action = "auth1" + auth_type = "password" / "rsa" / "rhosts-rsa" / "rhosts" / + "challenge-response" / "kerberos" + +Upon pubkey verification, the public key is added as an authorizer. SSH +Protocol 1 RSA public keys are encoded in the form: + + sshkey:rsa1_142789799855908362[...]11720372574856523841 + +Protocol 2: + + action = "auth2" + auth_type = "none" / "publickey" / "password" / + "keyboard-interactive" / "hostbased" + +SSH Protocol 2 keys are encoded as: + + sshkey:ssh-rsa_AAAAB3NzaC1yc2a[...]pMauaWV+G2LBcYtfYD8= + +i.e the public key algorithm name followed by the base 64 encoded public key. + +Remote '-R' port forwarding setup - tested when port forwarding was requested + + action = "rport-forward" + listen_port + +Port forwarding connect - check in channel_post_port_listener after accept() + + action = "lport-forward" + target_host + target_port + +Command execution + + action = "exec" + command (may have been modified by forced commands in public key files) + original_command (original command specified by user) + +Shell execution + + action = "shell" + command (will only be present if set by forced commands) + +Subsystem execution + + action = "subsys" + subsys + +Agent forwarding + + action = "agent-forward" + +PTY request + + action = "pty-request" + +SSH1 compression request + + action = "request-compression1" + compression_level = (1 -> 9) + +X11 forwarding request + action = "x11-forward" + + +An example sshd_policy showing most of the options: + +keynote-version: 2 +authorizer: "POLICY" +Conditions: + (app_domain == "SSH") -> { + action == "agent-forward" -> "true"; + action == "auth1" -> { + auth_type == "password" -> "true"; + auth_type == "rsa" -> "true"; + auth_type == "rhosts-rsa" -> "true"; + auth_type == "rhosts" -> "true"; + auth_type == "challenge-response" -> "true"; + auth_type == "kerberos" -> "true"; + }; + action == "auth2" -> { + auth_type == "none" -> "true"; + auth_type == "publickey" -> "false"; + auth_type == "password" -> "true"; + auth_type == "keyboard-interactive" -> "true"; + auth_type == "hostbased" -> "true"; + }; + action == "exec" -> "true"; + action == "lport-forward" -> "true"; + action == "pty-request" -> "true"; + action == "request-compression1" -> "true"; + action == "rport-forward" -> "true"; + action == "shell" -> "true"; + action == "subsys" -> "true"; + action == "x11-forward" -> "true"; + }; + +Authorizer: "POLICY" +Licensees: "sshkey:ssh-rsa_AAAAB3NzaC1yc2EAAAABIwAAAIEA0zFnigHloAYTCsmLgBsHZeJBjs7Q6LCjjBAXe/vPrHh0SfPZIrcpWxuqbuOniXJ0RNmwJMvq/cVIVF1pS/dgtmkgeP/5I1xPX6IvAtJsNi8Df23ypiV++QVxHd76FM683ZzyZC2SBv4nkpXWMuCaapxx0MauaWV+G2LBcYtfYD8=" +Conditions: + (app_domain == "SSH") -> { + action == "auth2" -> { + auth_type == "publickey" -> "true"; + }; + }; + }; + Index: auth.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/auth.h,v retrieving revision 1.26 diff -u -u -r1.26 auth.h --- auth.h 27 Dec 2001 19:54:53 -0000 1.26 +++ auth.h 14 Feb 2002 11:24:10 -0000 @@ -146,6 +146,8 @@ check_key_in_hostfiles(struct passwd *, Key *, const char *, const char *, const char *); +char *get_authname1(int); + #define AUTH_FAIL_MAX 6 #define AUTH_FAIL_LOG (AUTH_FAIL_MAX/2) #define AUTH_FAIL_MSG "Too many authentication failures for %.100s" Index: auth1.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/auth1.c,v retrieving revision 1.35 diff -u -u -r1.35 auth1.c --- auth1.c 3 Feb 2002 17:53:25 -0000 1.35 +++ auth1.c 14 Feb 2002 11:24:11 -0000 @@ -27,6 +27,10 @@ #include "misc.h" #include "uidswap.h" +#ifdef KEYNOTE +#include "policy.h" +#endif + /* import */ extern ServerOptions options; @@ -84,6 +88,9 @@ #if defined(KRB4) || defined(KRB5) (!options.kerberos_authentication || options.kerberos_or_local_passwd) && #endif +#ifdef KEYNOTE + policy_auth1("none", NULL) && +#endif auth_password(authctxt, "")) { auth_log(authctxt, 1, "without authentication", ""); return; @@ -99,6 +106,7 @@ authenticated = 0; info[0] = '\0'; + n = NULL; /* Get a packet from the client. */ type = packet_read(); @@ -209,6 +217,11 @@ authenticated = auth_rhosts_rsa(pw, client_user, client_host_key); +#ifdef KEYNOTE + if ((n = BN_new()) == NULL) + fatal("do_authloop: BN_new failed"); + BN_copy(n, client_host_key->rsa->n); +#endif key_free(client_host_key); snprintf(info, sizeof info, " ruser %.100s", client_user); @@ -226,7 +239,6 @@ packet_get_bignum(n); packet_check_eom(); authenticated = auth_rsa(pw, n); - BN_clear_free(n); break; case SSH_CMSG_AUTH_PASSWORD: @@ -299,6 +311,15 @@ !auth_root_allowed(get_authname(type))) authenticated = 0; +#ifdef KEYNOTE + if (authenticated && !policy_auth1(get_authname(type), n)) + authenticated = 0; + if (n != NULL) { + BN_clear_free(n); + n = NULL; + } +#endif + /* Log before sending the reply */ auth_log(authctxt, authenticated, get_authname(type), info); @@ -349,6 +370,9 @@ if (pw && allowed_user(pw)) { authctxt->valid = 1; pw = pwcopy(pw); +#ifdef KEYNOTE + policy_setup_user(pw, options.load_user_policies); +#endif } else { debug("do_authentication: illegal user %s", user); pw = NULL; Index: auth2.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/auth2.c,v retrieving revision 1.84 diff -u -u -r1.84 auth2.c --- auth2.c 4 Feb 2002 11:58:10 -0000 1.84 +++ auth2.c 14 Feb 2002 11:24:12 -0000 @@ -52,6 +52,10 @@ #include "canohost.h" #include "match.h" +#ifdef KEYNOTE +#include "policy.h" +#endif + /* import */ extern ServerOptions options; extern u_char *session_id2; @@ -187,6 +191,9 @@ authctxt->pw = pwcopy(pw); authctxt->valid = 1; debug2("input_userauth_request: setting up authctxt for %s", user); +#ifdef KEYNOTE + policy_setup_user(pw, options.load_user_policies); +#endif } else { log("input_userauth_request: illegal user %s", user); } @@ -209,6 +216,10 @@ if (m != NULL) { debug2("input_userauth_request: try method %s", method); authenticated = m->userauth(authctxt); +#ifdef KEYNOTE + if (authenticated && !policy_auth2(method)) + authenticated = 0; +#endif } userauth_finish(authctxt, authenticated, method); @@ -416,8 +427,13 @@ #endif /* test for correct signature */ if (user_key_allowed(authctxt->pw, key) && - key_verify(key, sig, slen, buffer_ptr(&b), buffer_len(&b)) == 1) + key_verify(key, sig, slen, buffer_ptr(&b), + buffer_len(&b)) == 1) { +#ifdef KEYNOTE + policy_add_pubkey2_authorizer(key); +#endif authenticated = 1; + } buffer_clear(&b); xfree(sig); } else { Index: channels.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.c,v retrieving revision 1.167 diff -u -u -r1.167 channels.c --- channels.c 6 Feb 2002 14:55:15 -0000 1.167 +++ channels.c 14 Feb 2002 11:24:19 -0000 @@ -1940,23 +1940,12 @@ } void -channel_input_port_open(int type, u_int32_t seq, void *ctxt) +channel_process_input_port_open(int remote_id, char *host, + u_short host_port, char *originator_string) { Channel *c = NULL; - u_short host_port; - char *host, *originator_string; - int remote_id, sock = -1; + int sock = -1; - remote_id = packet_get_int(); - host = packet_get_string(NULL); - host_port = packet_get_int(); - - if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { - originator_string = packet_get_string(NULL); - } else { - originator_string = xstrdup("unknown (remote did not supply name)"); - } - packet_check_eom(); sock = channel_connect_to(host, host_port); if (sock != -1) { c = channel_new("connected socket", @@ -1969,6 +1958,29 @@ packet_put_int(remote_id); packet_send(); } +} + +void +channel_input_port_open(int type, u_int32_t seq, void *ctxt) +{ + u_short host_port; + char *host, *originator_string; + int remote_id; + + remote_id = packet_get_int(); + host = packet_get_string(NULL); + host_port = packet_get_int(); + + if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { + originator_string = packet_get_string(NULL); + } else { + originator_string = xstrdup("unknown (remote did not supply name)"); + } + packet_check_eom(); + + channel_process_input_port_open(remote_id, host, host_port, + originator_string); + xfree(host); } @@ -2154,7 +2166,8 @@ */ void -channel_input_port_forward_request(int is_root, int gateway_ports) +channel_input_port_forward_request(int is_root, int gateway_ports, + int (*policy_callback)(u_short)) { u_short port, host_port; char *hostname; @@ -2164,13 +2177,18 @@ hostname = packet_get_string(NULL); host_port = packet_get_int(); + if (policy_callback != NULL && !policy_callback(port)) + packet_disconnect("Requested for forwarding of port " + "%d denied.", port); + /* * Check that an unprivileged user is not trying to forward a * privileged port. */ if (port < IPPORT_RESERVED && !is_root) - packet_disconnect("Requested forwarding of port %d but user is not root.", - port); + packet_disconnect("Requested forwarding of port %d but user " + "is not root.", port); + /* Initiate forwarding */ channel_setup_local_fwd_listener(port, hostname, host_port, gateway_ports); Index: channels.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.h,v retrieving revision 1.63 diff -u -u -r1.63 channels.h --- channels.h 5 Feb 2002 14:32:55 -0000 1.63 +++ channels.h 14 Feb 2002 11:24:19 -0000 @@ -165,6 +165,8 @@ void channel_input_port_open(int, u_int32_t, void *); void channel_input_window_adjust(int, u_int32_t, void *); +void channel_process_input_port_open(int, char *, u_short, char *); + /* file descriptor handling (read/write) */ void channel_prepare_select(fd_set **, fd_set **, int *, int*, int); @@ -182,7 +184,7 @@ void channel_permit_all_opens(void); void channel_add_permitted_opens(char *, int); void channel_clear_permitted_opens(void); -void channel_input_port_forward_request(int, int); +void channel_input_port_forward_request(int, int, int (*)(u_short)); int channel_connect_to(const char *, u_short); int channel_connect_by_listen_address(u_short); void channel_request_remote_forwarding(u_short, const char *, u_short); Index: packet.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/packet.c,v retrieving revision 1.87 diff -u -u -r1.87 packet.c --- packet.c 24 Jan 2002 21:13:23 -0000 1.87 +++ packet.c 14 Feb 2002 11:24:22 -0000 @@ -1287,3 +1287,9 @@ rand >>= 8; } } + +Newkeys * +packet_get_newkeys(int mode) +{ + return newkeys[mode]; +} Index: packet.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/packet.h,v retrieving revision 1.32 diff -u -u -r1.32 packet.h --- packet.h 28 Dec 2001 14:50:54 -0000 1.32 +++ packet.h 14 Feb 2002 11:24:22 -0000 @@ -17,6 +17,7 @@ #define PACKET_H #include +#include "kex.h" void packet_set_connection(int, int); void packet_set_nonblocking(void); @@ -69,6 +70,8 @@ void tty_make_modes(int, struct termios *); void tty_parse_modes(int, int *); + +Newkeys *packet_get_newkeys(int); extern int max_packet_size; int packet_set_maxsize(int); Index: pathnames.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/pathnames.h,v retrieving revision 1.11 diff -u -u -r1.11 pathnames.h --- pathnames.h 9 Feb 2002 17:37:34 -0000 1.11 +++ pathnames.h 14 Feb 2002 11:24:22 -0000 @@ -36,6 +36,7 @@ #define _PATH_DH_MODULI ETCDIR "/moduli" /* Backwards compatibility */ #define _PATH_DH_PRIMES ETCDIR "/primes" +#define _PATH_SERVER_POLICY_FILE SSHDIR "/sshd_policy" #define _PATH_SSH_PROGRAM "/usr/bin/ssh" @@ -46,7 +47,7 @@ #define _PATH_SSH_DAEMON_PID_FILE _PATH_SSH_PIDDIR "/sshd.pid" /* - * The directory in user\'s home directory in which the files reside. The + * The directory in user's home directory in which the files reside. The * directory should be world-readable (though not all files are). */ #define _PATH_SSH_USER_DIR ".ssh" @@ -69,17 +70,23 @@ #define _PATH_SSH_CLIENT_ID_RSA ".ssh/id_rsa" /* - * Configuration file in user\'s home directory. This file need not be + * Configuration file in user's home directory. This file need not be * readable by anyone but the user him/herself, but does not contain anything - * particularly secret. If the user\'s home directory resides on an NFS + * particularly secret. If the user's home directory resides on an NFS * volume where root is mapped to nobody, this may need to be world-readable. */ #define _PATH_SSH_USER_CONFFILE ".ssh/config" /* + * Keynote policy file in user's home directory. This file should not be + * readable by anyone other than the user (i.e mode 0600). + */ +#define _PATH_SSH_USER_POLICY_FILE ".ssh/policy" + +/* * File containing a list of those rsa keys that permit logging in as this * user. This file need not be readable by anyone but the user him/herself, - * but does not contain anything particularly secret. If the user\'s home + * but does not contain anything particularly secret. If the user's home * directory resides on an NFS volume where root is mapped to nobody, this * may need to be world-readable. (This file is read by the daemon which is * running as root.) Index: policy.c =================================================================== RCS file: policy.c diff -N policy.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ policy.c 14 Feb 2002 11:24:23 -0000 @@ -0,0 +1,459 @@ +/* + * Copyright (c) 2001,2002 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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" + +#ifdef KEYNOTE + +#include "ssh.h" +#include "log.h" +#include "atomicio.h" +#include "pathnames.h" +#include "xmalloc.h" +#include "key.h" +#include "policy.h" +#include "auth.h" + +#include +#include + +RCSID("$Id$"); + +#define MAX_FILE_LENGTH (1024 * 256) + +static int keynote_id = -1; + +/* + * Possible results from policy query + */ +#define NUM_POLICY_RESULTS 2 +char *policy_results_str[] = { "false", "true" }; +enum policy_result { + POLICY_FALSE = 0, + POLICY_TRUE = 1 +}; + +static int +add_assertions(int kid, char *s, size_t len, int trusted) +{ + char **a; + int num_assertions; + int i; + + a = kn_read_asserts(s, len, &num_assertions); + if (a == NULL) { + error("Couldn't split assertions: %d", keynote_errno); + return -1; + } + + debug2("policy - Found %d assertions", num_assertions); + for (i = 0; i < num_assertions; i++) { + if (kn_add_assertion(kid, a[i], strlen(a[i]), + trusted?ASSERT_FLAG_LOCAL:0) == -1) { + error("Couldn't add assertion %d: %d", i, + keynote_errno); + } + free(a[i]); + } + free(a); + + return 0; +} + +static int +add_assertions_from_file(int kid, const char *path, int trusted, + uid_t expected_owner) +{ + int fd; + struct stat st; + char *buf; + + debug2("policy - Reading keynote policies from %.200s", path); + + if ((fd = open(path, O_RDONLY)) == -1) { + debug("policy - Couldn't open \"%s\": %s", path, + strerror(errno)); + return -1; + } + + if (fstat(fd, &st) == -1) + fatal("Couldn't stat \"%s\": %s", path, strerror(errno)); + + if (!S_ISREG(st.st_mode)) + fatal("\"%s\" is not a regular file", path); + + if (st.st_size >= MAX_FILE_LENGTH) + fatal("File \"%s\" is longer than maximum permitted " + "length %u", path, MAX_FILE_LENGTH); + + if (st.st_uid != expected_owner) + fatal("\"%s\" has incorrect ownership", path); + + if ((st.st_mode & 0777) != 0600) + fatal("\"%s\" has incorrect permissions", path); + + buf = xmalloc(st.st_size + 1); + + if (atomicio(read, fd, buf, st.st_size) != st.st_size) { + free(buf); + fatal("Couldn't read \"%s\": %s", path, strerror(errno)); + } + + close(fd); + + buf[st.st_size] = '\0'; + + if (add_assertions(kid, buf, st.st_size, trusted) == -1) + return -1; + + free(buf); + + return 0; +} + +void +policy_del_action(const char *name) +{ + char *n; + + /* Duplicate so we can have const args */ + n = xstrdup(name); + + debug3("policy - Deleting variable %s", n); + if (kn_remove_action(keynote_id, n) == -1 && + keynote_errno != ERROR_NOTFOUND) + fatal("kn_remove_action failed, error %d", keynote_errno); + + xfree(n); +} + +void +policy_add_action(const char *name, const char *value) +{ + char *n, *v; + + /* Duplicate so we can have const args */ + n = xstrdup(name); + v = xstrdup(value); + + debug2("policy - Adding variable %s = \"%s\"", n, v); + if (kn_add_action(keynote_id, n, v, 0) == -1) + fatal("kn_add_action failed, error %d", keynote_errno); + + xfree(n); + xfree(v); +} + +void +policy_add_action_int(const char *name, int value) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "%d", value); + + policy_add_action(name, buf); +} + +void +policy_init(void) +{ + keynote_id = kn_init(); + if (keynote_id == -1) + fatal("kn_init failed, error %d", keynote_errno); + + if (add_assertions_from_file(keynote_id, _PATH_SERVER_POLICY_FILE, + 1, 0) == -1) + fatal("Couldn't read system policies"); + + policy_add_action("app_domain", "SSH"); + + if (kn_add_authorizer(keynote_id, "SSHD") == -1) + fatal("kn_add_authorizer failed, error %d", keynote_errno); +} + +void +policy_setup_user(struct passwd *pw, int add_user_asserts) +{ + char buf[1024]; + struct group *gp; + time_t t; + struct tm *tm; + + if (add_user_asserts) { + snprintf(buf, sizeof(buf), "%.100s/%.100s/%.100s", pw->pw_dir, + _PATH_SSH_USER_DIR, _PATH_SSH_USER_POLICY_FILE); + add_assertions_from_file(keynote_id, buf, 1, pw->pw_uid); + } + + /* Add user-specific and generic action variables */ + policy_add_action("user", pw->pw_name); + + snprintf(buf, sizeof(buf), "%d", pw->pw_uid); + policy_add_action("uid", buf); + + gp = getgrgid(pw->pw_gid); + if (gp) + policy_add_action("group", gp->gr_name); + + snprintf(buf, sizeof(buf), "%d", pw->pw_gid); + policy_add_action("gid", buf); + + policy_add_action("shell", pw->pw_shell); + + t = time(NULL); + tm = localtime(&t); + + snprintf(buf, sizeof(buf), "%02d%02d%02d", tm->tm_hour, + tm->tm_min, tm->tm_sec); + policy_add_action("time_of_day", buf); + + snprintf(buf, sizeof(buf), "%d", tm->tm_wday); + policy_add_action("day_of_week", buf); + + snprintf(buf, sizeof(buf), "%04d%02d%02d", tm->tm_year + 1900, + tm->tm_mon + 1, tm->tm_mday); + policy_add_action("date", buf); +} + +int +policy_check(const char *action_name) +{ + int ret; + + policy_add_action("action", action_name); + + ret = kn_do_query(keynote_id, policy_results_str, NUM_POLICY_RESULTS); + debug2("policy - Test ret=%d", ret); + + policy_del_action("action"); + + if (ret == -1) + fatal("KeyNote policy test failed, error %d", keynote_errno); + + if (ret != POLICY_TRUE) { + log("Action \"%s\" denied by policy", action_name); + return 0; + } + + return 1; +} + +static int +policy_auth_check(const char *action, const char *authtype) +{ + int ret; + + policy_add_action("action", action); + policy_add_action("auth_type", authtype); + + ret = kn_do_query(keynote_id, policy_results_str, NUM_POLICY_RESULTS); + debug2("policy - %s test ret=%d", action, ret); + + policy_del_action("auth_type"); + policy_del_action("action"); + + if (ret == -1) + fatal("KeyNote policy test failed, error %d", keynote_errno); + + if (ret != POLICY_TRUE) { + log("Authentication \"%s:%s\" denied by policy", + action, authtype); + return 0; + } + + return 1; +} + +int +policy_exec_check(const char *command, const char *original_command) +{ + int ret; + + policy_add_action("command", command != NULL ? command : ""); + policy_add_action("original_command", + original_command != NULL ? original_command : ""); + + if (original_command == NULL) + ret = policy_check("shell"); + else + ret = policy_check("exec"); + + policy_del_action("command"); + policy_del_action("original_command"); + + return(ret); +} + +int +policy_rportfwd_check(u_short port) +{ + int ret; + + policy_add_action_int("listen_port", port); + ret = policy_check("rport-forward"); + policy_del_action("listen_port"); + + return ret; +} + +int +policy_lportfwd_check(char *target_host, u_short target_port) +{ + int ret; + + policy_add_action("target_host", target_host); + policy_add_action_int("target_port", target_port); + ret = policy_check("lport-forward"); + policy_del_action("target_host"); + policy_del_action("target_port"); + + return ret; +} + +static char * +policy_key1_to_string(BIGNUM *client_n) +{ + char buf[1024], *ret, *n; + + if ((n = BN_bn2dec(client_n)) == NULL) { + error("policy_key1_to_string: BN_bn2dec failed"); + return NULL; + } + + snprintf(buf, sizeof(buf), "sshkey:rsa1_%s", n); + ret = xstrdup(buf); + + OPENSSL_free(n); + + return ret; +} + +static char * +policy_key2_to_string(Key *key) +{ + char buf[2048], *ret; + + /* + * NB. We only handle protocol 2 keys, as RSA1 keys are checked + * directly by auth_rsa and are never converted to a Key* + */ + + ret = NULL; + if ((key->type == KEY_DSA && key->dsa != NULL) || + (key->type == KEY_RSA && key->rsa != NULL)) { + int len, n; + u_char *blob, *uu; + + key_to_blob(key, &blob, &len); + uu = xmalloc(2 * len); + n = uuencode(blob, len, uu, 2 * len); + if (n <= 0) { + error("key_to_string: uuencode failed"); + return NULL; + } + snprintf(buf, sizeof(buf), "sshkey:%s_%s", + key_ssh_name(key), uu); + ret = xstrdup(buf); + xfree(blob); + xfree(uu); + } + return ret; +} + +static void +policy_del_authorizer(char *authorizer) +{ + debug3("policy - Removing authorizer: %s", authorizer); + if (kn_remove_authorizer(keynote_id, authorizer) == -1) + fatal("kn_remove_authorizer failed, error %d", keynote_errno); +} + +static void +policy_add_authorizer(char *authorizer) +{ + debug2("policy - Adding authorizer: %s", authorizer); + if (kn_add_authorizer(keynote_id, authorizer) == -1) + fatal("kn_add_authorizer failed, error %d", keynote_errno); +} + +static int +policy_auth_check_pubkey1(const char *authtype, BIGNUM *n) +{ + char *authorizer; + int ret; + + if ((authorizer = policy_key1_to_string(n)) == NULL) + return 0; + + /* + * Don't delete authorizers once we have added them. We only add + * _verified_ keys as authorizers. If they are left around, you can + * delegate post-authentication policy to them. + */ + policy_add_authorizer(authorizer); + xfree(authorizer); + + ret = policy_auth_check("auth1", authtype); + + return ret; +} + +int +policy_add_pubkey2_authorizer(Key *k) +{ + char *authorizer; + int ret; + + if ((authorizer = policy_key2_to_string(k)) == NULL) + return 0; + + /* + * Don't delete authorizers once we have added them. We only add + * _verified_ keys as authorizers. If they are left around, you can + * delegate post-authentication policy to them. + */ + policy_add_authorizer(authorizer); + xfree(authorizer); + + return 1; +} + +int +policy_auth1(const char *authtype, BIGNUM *n) +{ + if (!strcmp(authtype, "rhosts-rsa") || !strcmp(authtype, "rsa")) + return policy_auth_check_pubkey1(authtype, n); + else + return policy_auth_check("auth1", authtype); +} + +int +policy_auth2(const char *authtype) +{ + return policy_auth_check("auth2", authtype); +} + +#endif /* KEYNOTE */ Index: policy.h =================================================================== RCS file: policy.h diff -N policy.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ policy.h 14 Feb 2002 11:24:23 -0000 @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2001,2002 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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. + */ + +void policy_del_action(const char *name); +void policy_add_action(const char *name, const char *value); +void policy_add_action_int(const char *name, int value); +void policy_init(void); +void policy_setup_user(struct passwd *pw, int add_user_asserts); +int policy_check(const char *action_name); +int policy_auth1(const char *authtype, BIGNUM *n); +int policy_auth2(const char *authtype); +int policy_exec_check(const char *command, const char *original_command); +int policy_rportfwd_check(u_short port); +int policy_lportfwd_check(char *target_host, u_short target_port); +int policy_add_pubkey2_authorizer(Key *k); Index: servconf.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/servconf.c,v retrieving revision 1.101 diff -u -u -r1.101 servconf.c --- servconf.c 4 Feb 2002 12:15:25 -0000 1.101 +++ servconf.c 14 Feb 2002 11:24:26 -0000 @@ -105,6 +105,9 @@ options->client_alive_count_max = -1; options->authorized_keys_file = NULL; options->authorized_keys_file2 = NULL; +#ifdef KEYNOTE + options->load_user_policies = -1; +#endif } void @@ -225,6 +228,10 @@ } if (options->authorized_keys_file == NULL) options->authorized_keys_file = _PATH_SSH_USER_PERMITTED_KEYS; +#ifdef KEYNOTE + if (options->load_user_policies == -1) + options->load_user_policies = 0; +#endif } /* Keyword tokens. */ @@ -254,6 +261,9 @@ sBanner, sVerifyReverseMapping, sHostbasedAuthentication, sHostbasedUsesNameFromPacketOnly, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2, +#ifdef KEYNOTE + sLoadUserPolicies, +#endif sDeprecated } ServerOpCodes; @@ -326,6 +336,9 @@ { "clientalivecountmax", sClientAliveCountMax }, { "authorizedkeysfile", sAuthorizedKeysFile }, { "authorizedkeysfile2", sAuthorizedKeysFile2 }, +#ifdef KEYNOTE + { "loaduserpolicies", sLoadUserPolicies }, +#endif { NULL, sBadOption } }; @@ -834,6 +847,12 @@ case sClientAliveCountMax: intptr = &options->client_alive_count_max; goto parse_int; + +#ifdef KEYNOTE + case sLoadUserPolicies: + intptr = &options->load_user_policies; + goto parse_flag; +#endif case sDeprecated: log("%s line %d: Deprecated option %s", Index: servconf.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/servconf.h,v retrieving revision 1.53 diff -u -u -r1.53 servconf.h --- servconf.h 29 Jan 2002 14:32:03 -0000 1.53 +++ servconf.h 14 Feb 2002 11:24:27 -0000 @@ -129,6 +129,9 @@ char *authorized_keys_file; /* File containing public keys */ char *authorized_keys_file2; +#ifdef KEYNOTE + int load_user_policies; +#endif } ServerOptions; Index: serverloop.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/serverloop.c,v retrieving revision 1.98 diff -u -u -r1.98 serverloop.c --- serverloop.c 6 Feb 2002 14:55:16 -0000 1.98 +++ serverloop.c 14 Feb 2002 11:24:29 -0000 @@ -55,6 +55,10 @@ #include "misc.h" #include "kex.h" +#ifdef KEYNOTE +#include "policy.h" +#endif + extern ServerOptions options; /* XXX */ @@ -845,7 +849,7 @@ } static Channel * -server_request_direct_tcpip(char *ctype) +server_request_direct_tcpip(char *ctype, const char **reason) { Channel *c; int sock; @@ -861,12 +865,23 @@ debug("server_request_direct_tcpip: originator %s port %d, target %s port %d", originator, originator_port, target, target_port); +#ifdef KEYNOTE + if (!policy_lportfwd_check(target, target_port)) { + xfree(target); + xfree(originator); + *reason = "permission denied"; + return(NULL); + } +#endif + /* XXX check permission */ sock = channel_connect_to(target, target_port); xfree(target); xfree(originator); - if (sock < 0) + if (sock < 0) { + *reason = "Connection failed"; return NULL; + } c = channel_new(ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, xstrdup("direct-tcpip"), 1); @@ -874,7 +889,7 @@ } static Channel * -server_request_session(char *ctype) +server_request_session(char *ctype, const char **reason) { Channel *c; @@ -890,6 +905,7 @@ -1, -1, -1, /*window size*/0, CHAN_SES_PACKET_DEFAULT, 0, xstrdup("server-session"), 1); if (session_open(xxx_authctxt, c->self) != 1) { + *reason = "session open failed"; debug("session open failed, free channel %d", c->self); channel_free(c); return NULL; @@ -907,6 +923,7 @@ int rchan; int rmaxpack; int rwindow; + const char *reason; ctype = packet_get_string(&len); rchan = packet_get_int(); @@ -916,10 +933,11 @@ debug("server_input_channel_open: ctype %s rchan %d win %d max %d", ctype, rchan, rwindow, rmaxpack); + reason = NULL; if (strcmp(ctype, "session") == 0) { - c = server_request_session(ctype); + c = server_request_session(ctype, &reason); } else if (strcmp(ctype, "direct-tcpip") == 0) { - c = server_request_direct_tcpip(ctype); + c = server_request_direct_tcpip(ctype, &reason); } if (c != NULL) { debug("server_input_channel_open: confirm %s", ctype); @@ -940,7 +958,7 @@ packet_put_int(rchan); packet_put_int(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED); if (!(datafellows & SSH_BUG_OPENFAILURE)) { - packet_put_cstring("open failed"); + packet_put_cstring(reason ? reason : "open failed"); packet_put_cstring(""); } packet_send(); @@ -975,7 +993,10 @@ /* check permissions */ if (!options.allow_tcp_forwarding || - no_port_forwarding_flag || + no_port_forwarding_flag || +#ifdef KEYNOTE + !policy_rportfwd_check(listen_port) || +#endif (listen_port < IPPORT_RESERVED && pw->pw_uid != 0)) { success = 0; packet_send_debug("Server has disabled port forwarding."); @@ -1023,6 +1044,40 @@ } static void +server_input_port_open(int type, u_int32_t seq, void *ctxt) +{ + u_short host_port; + char *host, *originator_string; + int remote_id; + + remote_id = packet_get_int(); + host = packet_get_string(NULL); + host_port = packet_get_int(); + + if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { + originator_string = packet_get_string(NULL); + } else { + originator_string = xstrdup("unknown (remote did not supply name)"); + } + packet_check_eom(); + +#ifdef KEYNOTE + if (policy_lportfwd_check(host, host_port)) + channel_process_input_port_open(remote_id, host, host_port, + originator_string); + else { + xfree(originator_string); + packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(remote_id); + packet_send(); + } +#else + channel_process_input_port_open(remote_id, host, host_port, + originator_string); +#endif +} + +static void server_init_dispatch_20(void) { debug("server_init_dispatch_20"); @@ -1055,7 +1110,7 @@ dispatch_set(SSH_MSG_CHANNEL_DATA, &channel_input_data); dispatch_set(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); dispatch_set(SSH_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); - dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open); + dispatch_set(SSH_MSG_PORT_OPEN, &server_input_port_open); } static void server_init_dispatch_15(void) Index: session.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/session.c,v retrieving revision 1.125 diff -u -u -r1.125 session.c --- session.c 9 Feb 2002 17:37:34 -0000 1.125 +++ session.c 14 Feb 2002 11:24:34 -0000 @@ -57,6 +57,10 @@ #include "canohost.h" #include "session.h" +#ifdef KEYNOTE +#include "policy.h" +#endif + /* types */ #define TTYSZ 64 @@ -206,9 +210,17 @@ case SSH_CMSG_REQUEST_COMPRESSION: compression_level = packet_get_int(); packet_check_eom(); +#ifdef KEYNOTE + policy_add_action_int("compression_level", + compression_level); + if (compression_level < 1 || compression_level > 9 || + !policy_check("request-compression1")) { + policy_del_action("compression_level"); +#else if (compression_level < 1 || compression_level > 9) { +#endif packet_send_debug("Received illegal compression level %d.", - compression_level); + compression_level); break; } /* Enable compression after we have responded with SUCCESS. */ @@ -252,6 +264,10 @@ break; } debug("Received authentication agent forwarding request."); +#ifdef KEYNOTE + if (!policy_check("agent-forward")) + break; +#endif success = auth_input_request_forwarding(s->pw); break; @@ -265,7 +281,13 @@ break; } debug("Received TCP/IP port forwarding request."); - channel_input_port_forward_request(s->pw->pw_uid == 0, options.gateway_ports); +#ifdef KEYNOTE + channel_input_port_forward_request(s->pw->pw_uid == 0, + options.gateway_ports, policy_rportfwd_check); +#else + channel_input_port_forward_request(s->pw->pw_uid == 0, + options.gateway_ports, NULL); +#endif success = 1; break; @@ -569,6 +591,11 @@ debug("Forced command '%.900s'", command); } +#ifdef KEYNOTE + if (!policy_exec_check(command, original_command)) + packet_disconnect("Execution not permitted"); +#endif + if (s->ttyfd != -1) do_exec_pty(s, command); else @@ -1232,6 +1259,11 @@ return 0; } +#ifdef KEYNOTE + if (!policy_check("pty-request")) + return 0; +#endif + s->term = packet_get_string(&len); if (compat20) { @@ -1367,6 +1399,12 @@ debug("session_auth_agent_req: no_agent_forwarding_flag"); return 0; } + +#ifdef KEYNOTE + if (!policy_check("agent-forward")) + return 0; +#endif + if (called) { return 0; } else { @@ -1642,6 +1680,10 @@ debug("X11 display already set."); return 0; } +#ifdef KEYNOTE + if (!policy_check("x11-forward")) + return 0; +#endif s->display_number = x11_create_display_inet(options.x11_display_offset, options.x11_use_localhost, s->single_connection); if (s->display_number == -1) { Index: sshd.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/sshd.c,v retrieving revision 1.226 diff -u -u -r1.226 sshd.c --- sshd.c 11 Feb 2002 16:19:39 -0000 1.226 +++ sshd.c 14 Feb 2002 11:24:37 -0000 @@ -73,6 +73,10 @@ #include "dispatch.h" #include "channels.h" +#ifdef KEYNOTE +#include "policy.h" +#endif + #ifdef LIBWRAP #include #include @@ -445,6 +449,10 @@ server_version_string, client_version_string); fatal_cleanup(); } +#ifdef KEYNOTE + policy_add_action_int("protocol", compat20 ? 2 : 1); + policy_add_action("client_version", remote_version); +#endif } @@ -708,6 +716,11 @@ /* Fill in default values for those options not explicitly set. */ fill_default_server_options(&options); +#ifdef KEYNOTE + /* Init Keynote policies */ + policy_init(); +#endif + /* Check that there are no remaining arguments. */ if (optind < ac) { fprintf(stderr, "Extra argument %s.\n", av[optind]); @@ -836,7 +849,7 @@ startup_pipe = -1; /* * We intentionally do not close the descriptors 0, 1, and 2 - * as our code for setting the descriptors won\'t work if + * as our code for setting the descriptors won't work if * ttyfd happens to be one of those. */ debug("inetd sockets after dupping: %d, %d", sock_in, sock_out); @@ -1159,11 +1172,17 @@ /* Log the connection. */ verbose("Connection from %.500s port %d", remote_ip, remote_port); +#ifdef KEYNOTE + policy_add_action("remote_ip", remote_ip); + snprintf(strport, sizeof(strport), "%d", remote_port); + policy_add_action("remote_port", strport); +#endif + /* - * We don\'t want to listen forever unless the other side + * We don't want to listen forever unless the other side * successfully authenticates itself. So we set up an alarm which is * cleared after successful authentication. A limit of zero - * indicates no limit. Note that we don\'t set the alarm in debugging + * indicates no limit. Note that we don't set the alarm in debugging * mode; it is just annoying to have the server exit just when you * are about to discover the bug. */ @@ -1438,6 +1457,10 @@ packet_start(SSH_SMSG_SUCCESS); packet_send(); packet_write_wait(); + +#ifdef KEYNOTE + policy_add_action("cipher1", cipher_name(cipher_type)); +#endif } /* @@ -1485,4 +1508,30 @@ packet_write_wait(); #endif debug("KEX done"); + +#ifdef KEYNOTE + policy_add_action("cipher2_ctos", + packet_get_newkeys(MODE_IN)->enc.name); + policy_add_action("mac2_ctos", + packet_get_newkeys(MODE_IN)->mac.name); + policy_add_action("comp_alg2_ctos", + packet_get_newkeys(MODE_IN)->comp.name); + policy_add_action("cipher2_stoc", + packet_get_newkeys(MODE_OUT)->enc.name); + policy_add_action("mac2_stoc", + packet_get_newkeys(MODE_OUT)->mac.name); + policy_add_action("comp_alg2_stoc", + packet_get_newkeys(MODE_OUT)->comp.name); + + switch(kex->kex_type) { + case DH_GRP1_SHA1: + policy_add_action("kex_type", KEX_DH1); + break; + case DH_GEX_SHA1: + policy_add_action("kex_type", KEX_DHGEX); + break; + default: + debug("XXX: Unknown kex type %d", kex->kex_type); + } +#endif /* KEYNOTE */ } Index: sshd_config =================================================================== RCS file: /cvs/src/usr.bin/ssh/sshd_config,v retrieving revision 1.47 diff -u -u -r1.47 sshd_config --- sshd_config 9 Feb 2002 17:37:34 -0000 1.47 +++ sshd_config 14 Feb 2002 11:24:37 -0000 @@ -49,6 +49,7 @@ # Change to yes if you don't trust ~/.ssh/known_hosts for # RhostsRSAAuthentication and HostbasedAuthentication #IgnoreUserKnownHosts no +#LoadUserPolicies no # To disable tunneled clear text passwords, change to no here! #PasswordAuthentication yes Index: sshd_policy =================================================================== RCS file: sshd_policy diff -N sshd_policy --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sshd_policy 14 Feb 2002 11:24:37 -0000 @@ -0,0 +1,4 @@ +keynote-version: 2 +authorizer: "POLICY" +conditions: app_domain == "SSH" -> "true"; + Index: sshd/Makefile =================================================================== RCS file: /cvs/src/usr.bin/ssh/sshd/Makefile,v retrieving revision 1.45 diff -u -u -r1.45 Makefile --- sshd/Makefile 7 Oct 2001 18:14:20 -0000 1.45 +++ sshd/Makefile 14 Feb 2002 11:24:37 -0000 @@ -7,7 +7,7 @@ BINMODE=555 BINDIR= /usr/sbin MAN= sshd.8 -CFLAGS+=-DHAVE_LOGIN_CAP -DBSD_AUTH +CFLAGS+=-DHAVE_LOGIN_CAP #-DBSD_AUTH SRCS= sshd.c auth-rhosts.c auth-passwd.c auth-rsa.c auth-rh-rsa.c \ sshpty.c sshlogin.c servconf.c serverloop.c \ @@ -16,6 +16,15 @@ auth-skey.c auth-bsdauth.c .include # for KERBEROS and AFS + +.ifdef KEYNOTE +.if (${KEYNOTE:L} == "yes") +SRCS+= policy.c +CFLAGS+=-DKEYNOTE +LDADD+= -lkeynote -lm +DPADD+= ${LIBKEYNOTE} ${LIBM} +.endif # KEYNOTE +.endif .if (${KERBEROS5:L} == "yes") CFLAGS+=-DKRB5 -I${DESTDIR}/usr/include/kerberosV