diff --git a/fb87_070_logging_reverse_port_forward.patch b/fb87_070_logging_reverse_port_forward.patch new file mode 100644 index 0000000000000000000000000000000000000000..a2555771ad1a192266d7d604ce37a426c517f226 --- /dev/null +++ b/fb87_070_logging_reverse_port_forward.patch @@ -0,0 +1,46 @@ +--- a/channels.c ++++ b/channels.c +@@ -3774,6 +3774,7 @@ int + channel_setup_remote_fwd_listener(struct ssh *ssh, struct Forward *fwd, + int *allocated_listen_port, struct ForwardOptions *fwd_opts) + { ++ int success = 0; + if (!check_rfwd_permission(ssh, fwd)) { + ssh_packet_send_debug(ssh, "port forwarding refused"); + if (fwd->listen_path != NULL) +@@ -3795,14 +3796,23 @@ channel_setup_remote_fwd_listener(struct + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); + return 0; + } +- if (fwd->listen_path != NULL) { +- return channel_setup_fwd_listener_streamlocal(ssh, ++ if (fwd->listen_path != NULL) { ++ success = channel_setup_fwd_listener_streamlocal(ssh, + SSH_CHANNEL_RUNIX_LISTENER, fwd, fwd_opts); + } else { +- return channel_setup_fwd_listener_tcpip(ssh, ++ success = channel_setup_fwd_listener_tcpip(ssh, + SSH_CHANNEL_RPORT_LISTENER, fwd, allocated_listen_port, + fwd_opts); + } ++ logit("Remote forward request %s: listen=%s:%d connect=%s:%d" ++ " uid=%d", ++ success ? "succeeded" : "failed", ++ fwd->listen_host, ++ fwd->listen_port, ++ ssh_remote_ipaddr(ssh), ++ ssh_remote_port(ssh), ++ getuid()); ++ return success; + } + + /* +@@ -4591,7 +4601,7 @@ x11_create_display_inet(struct ssh *ssh, + if ((errno != EINVAL) && (errno != EAFNOSUPPORT) + #ifdef EPFNOSUPPORT + && (errno != EPFNOSUPPORT) +-#endif ++#endif + ) { + error("socket: %.100s", strerror(errno)); + freeaddrinfo(aitop); diff --git a/fb87_080_logging_certificates.patch b/fb87_080_logging_certificates.patch new file mode 100644 index 0000000000000000000000000000000000000000..246217037566b805ddb1f7eb661ef07e8c155571 --- /dev/null +++ b/fb87_080_logging_certificates.patch @@ -0,0 +1,149 @@ +Index: openssh-9.9p1/auth2-pubkey.c +=================================================================== +--- openssh-9.9p1.orig/auth2-pubkey.c ++++ openssh-9.9p1/auth2-pubkey.c +@@ -524,11 +524,16 @@ user_cert_trusted_ca(struct passwd *pw, + options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) + return 0; + ++ ++ + if ((r = sshkey_in_file(key->cert->signature_key, + options.trusted_user_ca_keys, 1, 0)) != 0) { + debug2_fr(r, "CA %s %s is not listed in %s", + sshkey_type(key->cert->signature_key), ca_fp, + options.trusted_user_ca_keys); ++ verbose("CA %s %s is not listed in %s", ++ sshkey_type(key->cert->signature_key), ca_fp, ++ options.trusted_user_ca_keys); + goto out; + } + /* +@@ -540,6 +545,9 @@ user_cert_trusted_ca(struct passwd *pw, + if (match_principals_file(pw, principals_file, + key->cert, &principals_opts)) + found_principal = 1; ++ else { ++ verbose("Did not match any principals from auth_principals_* files"); ++ } + } + /* Try querying command if specified */ + if (!found_principal && match_principals_command(pw, key, +@@ -580,6 +588,11 @@ user_cert_trusted_ca(struct passwd *pw, + if ((final_opts = sshauthopt_merge(principals_opts, + cert_opts, &reason)) == NULL) { + fail_reason: ++ verbose("Rejected cert ID \"%s\" with signature " ++ "%s signed by %s CA %s via %s", ++ key->cert->key_id, ca_fp, ++ sshkey_type(key->cert->signature_key), ca_fp, ++ options.trusted_user_ca_keys); + error("%s", reason); + auth_debug_add("%s", reason); + goto out; +@@ -587,7 +600,7 @@ user_cert_trusted_ca(struct passwd *pw, + } + slog_set_cert_serial((unsigned long long)key->cert->serial); + /* Success */ +- verbose("Accepted certificate ID \"%s\" (serial %llu) signed by " ++ verbose("Accepted cert ID \"%s\" (serial %llu) signed by " + "%s CA %s via %s", key->cert->key_id, + (unsigned long long)key->cert->serial, + sshkey_type(key->cert->signature_key), ca_fp, +@@ -604,6 +617,7 @@ user_cert_trusted_ca(struct passwd *pw, + sshauthopt_free(final_opts); + free(principals_file); + free(ca_fp); ++ + return ret; + } + +Index: openssh-9.9p1/regress/cert-logging.sh +=================================================================== +--- /dev/null ++++ openssh-9.9p1/regress/cert-logging.sh +@@ -0,0 +1,84 @@ ++tid="cert logging" ++ ++CERT_ID="cert_id" ++PRINCIPAL=$USER ++SERIAL=0 ++ ++log_grep() { ++ if [ "$(grep -c -G "$1" "$TEST_SSHD_LOGFILE")" == "0" ]; then ++ return 1; ++ else ++ return 0; ++ fi ++} ++ ++cat << EOF >> $OBJ/sshd_config ++TrustedUserCAKeys $OBJ/ssh-rsa.pub ++Protocol 2 ++PubkeyAuthentication yes ++AuthenticationMethods publickey ++AuthorizedPrincipalsFile $OBJ/auth_principals ++EOF ++ ++if [ ! -f $OBJ/trusted_rsa ]; then ++ ${SSHKEYGEN} -q -t rsa -C '' -N '' -f $OBJ/trusted_rsa ++fi ++if [ ! -f $OBJ/untrusted_rsa ]; then ++ ${SSHKEYGEN} -q -t rsa -C '' -N '' -f $OBJ/untrusted_rsa ++fi ++ ++${SSHKEYGEN} -q -s $OBJ/ssh-rsa -I $CERT_ID -n $PRINCIPAL -z $SERIAL $OBJ/trusted_rsa.pub || ++ fatal "Could not create trusted SSH cert" ++ ++${SSHKEYGEN} -q -s $OBJ/untrusted_rsa -I $CERT_ID -n $PRINCIPAL -z $SERIAL $OBJ/untrusted_rsa.pub || ++ fatal "Could not create untrusted SSH cert" ++ ++CA_FP="$(${SSHKEYGEN} -l -E sha256 -f ssh-rsa | cut -d' ' -f2)" ++KEY_FP="$(${SSHKEYGEN} -l -E sha256 -f trusted_rsa | cut -d' ' -f2)" ++UNTRUSTED_CA_FP="$(${SSHKEYGEN} -l -E sha256 -f untrusted_rsa | cut -d' ' -f2)" ++ ++start_sshd ++ ++ ++test_no_principals() { ++ echo > $OBJ/auth_principals ++ ${SSH} -F $OBJ/ssh_config -i $OBJ/trusted_rsa-cert.pub somehost true || ++ fatal "SSH failed" ++ ++ if ! log_grep 'Did not match any principals from auth_principals_\* files'; then ++ fail "No 'Did not match any principals' message" ++ fi ++ ++ if ! log_grep "Rejected cert ID \"$CERT_ID\" with signature $KEY_FP signed by RSA CA $CA_FP via $OBJ/ssh-rsa.pub"; then ++ fail "No 'Rejected cert ID' message" ++ fi ++} ++ ++ ++test_with_principals() { ++ echo $USER > $OBJ/auth_principals ++ ${SSH} -F $OBJ/ssh_config -i $OBJ/trusted_rsa-cert.pub somehost true || ++ fatal "SSH failed" ++ ++ if ! log_grep "Matched principal \"$PRINCIPAL\" from $OBJ/auth_principals:1 against \"$PRINCIPAL\" from cert"; then ++ fail "No 'Matched principal' message" ++ fi ++ if ! log_grep "Accepted cert ID \"$CERT_ID\" (serial $SERIAL) with signature $KEY_FP signed by RSA CA $CA_FP via $OBJ/ssh-rsa.pub"; then ++ fail "No 'Accepted cert ID' message" ++ fi ++} ++ ++ ++test_untrusted_cert() { ++ ${SSH} -F $OBJ/ssh_config -i $OBJ/untrusted_rsa-cert.pub somehost true || ++ fatal "SSH failed" ++ ++ if ! log_grep "CA RSA $UNTRUSTED_CA_FP is not listed in $OBJ/ssh-rsa.pub"; then ++ fail "No 'CA is not listed' message" ++ fi ++} ++ ++ ++test_no_principals ++test_with_principals ++test_untrusted_cert diff --git a/fb87_090_logging_shell_cmd_pty.patch b/fb87_090_logging_shell_cmd_pty.patch new file mode 100644 index 0000000000000000000000000000000000000000..af7436f1e4afa778774477d8a04fdb64283b5c58 --- /dev/null +++ b/fb87_090_logging_shell_cmd_pty.patch @@ -0,0 +1,74 @@ +Index: openssh-9.9p1/session.c +=================================================================== +--- openssh-9.9p1.orig/session.c ++++ openssh-9.9p1/session.c +@@ -2037,6 +2037,8 @@ session_pty_req(struct ssh *ssh, Session + return 0; + } + debug("session_pty_req: session %d alloc %s", s->self, s->tty); ++ verbose("Allocated pty %s for user %s session %d", ++ s->tty, s->pw->pw_name, s->self); + + ssh_tty_parse_modes(ssh, s->ttyfd); + +@@ -2139,7 +2141,7 @@ session_shell_req(struct ssh *ssh, Sessi + sshpkt_fatal(ssh, r, "%s: parse packet", __func__); + + channel_set_xtype(ssh, s->chanid, "session:shell"); +- ++ verbose("Shell Request for user %s", s->pw->pw_name); + return do_exec(ssh, s, NULL) == 0; + } + +@@ -2156,6 +2158,7 @@ session_exec_req(struct ssh *ssh, Sessio + + channel_set_xtype(ssh, s->chanid, "session:command"); + slog_set_command(command); ++ verbose("Exec Request for user %s with command %s", s->pw->pw_name, command); + success = do_exec(ssh, s, command) == 0; + free(command); + return success; +Index: openssh-9.9p1/regress/session-req.sh +=================================================================== +--- /dev/null ++++ openssh-9.9p1/regress/session-req.sh +@@ -0,0 +1,39 @@ ++tid="session req" ++ ++start_sshd ++ ++test_user_shell_exec_req() { ++ session_shell_req_expected="Exec Request for user $USER with command true" ++ cnt=$(grep -c "$session_shell_req_expected" "$TEST_SSHD_LOGFILE") ++ if [ $cnt == "0" ]; then ++ fail "No exec request for user log lines found" ++ fi ++} ++ ++test_user_pty() { ++ session_pty_req_expected="Allocated pty .* for user $USER session .*" ++ line_count=$(grep -c "$session_req_expected" "$TEST_SSHD_LOGFILE") ++ if [ $line_count == "0" ]; then ++ fail "No Allocated pty for user session found in log lines" ++ fi ++} ++ ++test_user_shell_req() { ++ exit | ${SSH} -F $OBJ/ssh_config somehost ++ if [ $? -ne 0 ]; then ++ fail "ssh connect with failed" ++ fi ++ session_shell_req_expected="Shell Request for user $USER" ++ line_count=$(grep -c "$session_shell_req_expected" "$TEST_SSHD_LOGFILE") ++ if [ $line_count == "0" ]; then ++ fail "No session request for user log lines found" ++ fi ++} ++ ++${SSH} -F $OBJ/ssh_config somehost true ++if [ $? -ne 0 ]; then ++ fail "ssh connect with failed" ++fi ++test_user_shell_exec_req ++test_user_pty ++test_user_shell_req diff --git a/fb87_810_increase_ssh_cert_max_principals.patch b/fb87_810_increase_ssh_cert_max_principals.patch new file mode 100644 index 0000000000000000000000000000000000000000..82919c28afde20d1b17b8143960edf7ade7c32b9 --- /dev/null +++ b/fb87_810_increase_ssh_cert_max_principals.patch @@ -0,0 +1,11 @@ +--- a/sshkey.h ++++ b/sshkey.h +@@ -109,7 +109,7 @@ enum sshkey_private_format { + /* key is stored in external hardware */ + #define SSHKEY_FLAG_EXT 0x0001 + +-#define SSHKEY_CERT_MAX_PRINCIPALS 256 ++#define SSHKEY_CERT_MAX_PRINCIPALS 1024 + /* XXX opaquify? */ + struct sshkey_cert { + struct sshbuf *certblob; /* Kept around for use on wire */ diff --git a/fb87_log_accept_env.patch b/fb87_log_accept_env.patch new file mode 100644 index 0000000000000000000000000000000000000000..11bd761abd47b5f62f2420b1bb3907872e2165d4 --- /dev/null +++ b/fb87_log_accept_env.patch @@ -0,0 +1,20 @@ +--- a/session.c ++++ b/session.c +@@ -2210,7 +2210,7 @@ session_env_req(struct ssh *ssh, Session + + for (i = 0; i < options.num_accept_env; i++) { + if (match_pattern(name, options.accept_env[i])) { +- debug2("Setting env %d: %s=%s", s->num_env, name, val); ++ verbose("Setting env %d: %s=%s user=%s", s->num_env, name, val, s->pw->pw_name); + s->env = xrecallocarray(s->env, s->num_env, + s->num_env + 1, sizeof(*s->env)); + s->env[s->num_env].name = name; +@@ -2219,7 +2219,7 @@ session_env_req(struct ssh *ssh, Session + return (1); + } + } +- debug2("Ignoring env request %s: disallowed name", name); ++ verbose("Ignoring env request %s user=%s : disallowed name", name, s->pw->pw_name); + + fail: + free(name); diff --git a/fb87_log_auth_info.patch b/fb87_log_auth_info.patch new file mode 100644 index 0000000000000000000000000000000000000000..f317618ce39b73b3fe0d5807e384a2e61131e17b --- /dev/null +++ b/fb87_log_auth_info.patch @@ -0,0 +1,207 @@ +Index: openssh-9.9p1/regress/slog.sh +=================================================================== +--- openssh-9.9p1.orig/regress/slog.sh ++++ openssh-9.9p1/regress/slog.sh +@@ -1,41 +1,60 @@ + tid='structured log' + +-port="4242" + log_prefix="sshd_auth_msg:" +-log_keys="server_ip server_port remote_ip remote_port pid session_id method cert_id cert_serial principal user session_state auth_successful _time command end_time duration auth_info client_version" ++log_keys="server_ip server_port remote_ip remote_port pid session_id method cert_id cert_serial principal user session_state auth_successful command end_time duration auth_info client_version" + do_log_json="yes" +-test_config="$OBJ/sshd2_config" +-old_config="$OBJ/sshd_config" +-PIDFILE=$OBJ/pidfile +- +-cat << EOF > $test_config +- #*: +- StrictModes no +- Port $port +- AddressFamily inet +- ListenAddress 127.0.0.1 +- #ListenAddress ::1 +- PidFile $PIDFILE +- AuthorizedKeysFile $OBJ/authorized_keys_%u +- LogLevel ERROR +- AcceptEnv _XXX_TEST_* +- AcceptEnv _XXX_TEST +- HostKey $OBJ/host.ssh-ed25519 +- LogFormatPrefix $log_prefix +- LogFormatJson $do_log_json +- LogFormatKeys $log_keys ++ ++AUTH_PRINC_FILE="$OBJ/auth_principals" ++CA_FILE="$OBJ/ca-rsa" ++IDENTITY_FILE="$OBJ/$USER-rsa" ++CERT_ID=$USER ++ ++cat << EOF >> $OBJ/sshd_config ++TrustedUserCAKeys $CA_FILE.pub ++PubkeyAuthentication yes ++AuthenticationMethods publickey ++AuthorizedPrincipalsFile $AUTH_PRINC_FILE ++LogFormatPrefix $log_prefix ++LogFormatJson $do_log_json ++LogFormatKeys $log_keys + EOF + ++sed -i 's/DEBUG3/VERBOSE/g' $OBJ/sshd_config + +-cp $test_config $old_config +-start_sshd ++cleanup() { ++ rm -f $CA_FILE{.pub,} ++ rm -f $IDENTITY_FILE{-cert.pub,.pub,} ++ rm -f $AUTH_PRINC_FILE ++ rm -f $TEST_SSHD_LOGFILE ++} ++ ++make_keys() { ++ local keytype=$1 ++ ++ rm -f $IDENTITY_FILE{.pub,} ++ ${SSHKEYGEN} -q -t $keytype -C '' -N '' -f $IDENTITY_FILE || ++ fatal 'Could not create keypair' ++ ++ cat $IDENTITY_FILE.pub > authorized_keys_$USER ++ ${SSHKEYGEN} -lf $IDENTITY_FILE ++} + +-${SSH} -F $OBJ/ssh_config somehost true +-if [ $? -ne 0 ]; then +- fail "ssh connect with failed" +-fi ++make_cert() { ++ local princs=$1 ++ local certtype=$2 ++ local serial=$3 + +-test_log_counts() { ++ rm -f $CA_FILE ++ rm -f "$IDENTITY_FILE-cert.pub" ++ ++ ${SSHKEYGEN} -q -t $certtype -C '' -N '' -f $CA_FILE || ++ fatal 'Could not create CA key' ++ ++ ${SSHKEYGEN} -q -s $CA_FILE -I $CERT_ID -n "$princs" -z $serial "$IDENTITY_FILE.pub" || ++ fatal "Could not create SSH cert" ++} ++ ++do_test_log_counts() { + cnt=$(grep -c "$log_prefix" "$TEST_SSHD_LOGFILE") + if [ $cnt -ne 2 ]; then + fail "expected 2 structured logging lines, got $cnt" +@@ -43,7 +62,10 @@ test_log_counts() { + } + + test_json_valid() { +- which python &>/dev/null || echo 'python not found in path, skipping tests' ++ if ! $(which python &>/dev/null) ; then ++ echo 'python not found in path, skipping JSON tests' ++ return 1 ++ fi + + loglines=$(cat "$TEST_SSHD_LOGFILE" | grep "$log_prefix") + first=$(echo "$loglines" | head -n1) +@@ -55,5 +77,72 @@ test_json_valid() { + || fail "invalid json structure $last" + } + +-test_log_counts +-test_json_valid ++# todo: first/last line ++extract_key() { ++ local key=$1 ++ loglines=$(cat "$TEST_SSHD_LOGFILE" | grep "$log_prefix") ++ last=$(echo "$loglines" | tail -n1) ++ json=${last:$(expr length $log_prefix)} ++ ++ val=$(echo $json | python -c "import sys, json; print(json.load(sys.stdin)[\"$key\"])") || ++ fail "error extracting $key from $json" ++ echo "$val" ++} ++ ++test_basic_logging() { ++ ${SSH} -F $OBJ/ssh_config -v -i "$IDENTITY_FILE" somehost true || ++ fatal "SSH failed" ++ ++ do_test_log_counts ++ test_json_valid || return 1 ++} ++ ++extract_hash() { ++ local source=$1 ++ echo $source | sed "s/.*\(SHA256:[[:print:]]\{43\}\).*$/\1/" ++} ++ ++test_auth_info() { ++ local keyfp=$1 ++ local keytype=$2 ++ local princ=$3 ++ local serial=$4 ++ ++ ${SSH} -F $OBJ/ssh_config -v -i "$IDENTITY_FILE" somehost true || ++ fatal "SSH failed" ++ ++ auth_info=$(extract_key 'auth_info') ++ digest=$(extract_hash "$keyfp") ++ ++ [ -z "$keyfp" ] || echo "$auth_info" | grep -q "$digest" || ++ echo "hash digest not found" ++ [ -z "$keytype" ] || echo "$auth_info" | grep -q "$keytype" || ++ echo "keytype not found" ++ [ -z "$princ" ] || echo "$auth_info" | grep -q "$princ" || ++ echo "princ not found" ++ [ -z "$serial" ] || echo "$auth_info" | grep -q "$serial" || ++ echo "serial not found" ++} ++ ++test_cert_serial() { ++ local serial=$1 ++ logged_serial=$(extract_key 'cert_serial') ++ [ $serial = $logged_serial ] || fail 'cert serial mismatch' ++} ++ ++start_sshd ++ ++keytype="RSA" ++keyfp=$(make_keys $keytype) ++test_basic_logging || return ++test_auth_info "$keyfp" "$keytype" ++ ++rm authorized_keys_$USER # force cert auth ++ ++princ="$USER" ++echo $princ > $AUTH_PRINC_FILE ++ ++serial='42' ++make_cert "$princ" "$keytype" "$serial" ++test_auth_info "$keyfp" "$keytype" "$princ" "$serial" ++test_cert_serial "$serial" +Index: openssh-9.9p1/auth.c +=================================================================== +--- openssh-9.9p1.orig/auth.c ++++ openssh-9.9p1/auth.c +@@ -305,6 +305,8 @@ auth_log(struct ssh *ssh, int authentica + extra != NULL ? ": " : "", + extra != NULL ? extra : ""); + ++ if (extra != NULL) ++ slog_set_auth_info(extra); + free(extra); + slog_set_auth_data(authenticated, method, authctxt->user); + +Index: openssh-9.9p1/auth2-pubkey.c +=================================================================== +--- openssh-9.9p1.orig/auth2-pubkey.c ++++ openssh-9.9p1/auth2-pubkey.c +@@ -612,6 +612,8 @@ user_cert_trusted_ca(struct passwd *pw, + final_opts = NULL; + } + slog_set_cert_id(key->cert->key_id); ++ slog_set_cert_serial((unsigned long long) key->cert->serial); ++ + ret = 1; + out: + sshauthopt_free(principals_opts); diff --git a/fb87_log_port_forwards.patch b/fb87_log_port_forwards.patch new file mode 100644 index 0000000000000000000000000000000000000000..d43b85792eaff7b9502d45480dc2cff8d09b1f1f --- /dev/null +++ b/fb87_log_port_forwards.patch @@ -0,0 +1,22 @@ +--- a/serverloop.c ++++ b/serverloop.c +@@ -433,6 +433,7 @@ server_request_direct_tcpip(struct ssh * + char *target = NULL, *originator = NULL; + u_int target_port = 0, originator_port = 0; + int r; ++ uid_t user; + + if ((r = sshpkt_get_cstring(ssh, &target, NULL)) != 0 || + (r = sshpkt_get_u32(ssh, &target_port)) != 0 || +@@ -451,6 +452,11 @@ server_request_direct_tcpip(struct ssh * + goto out; + } + ++ user = getuid(); ++ logit("Tunnel: %s:%d -> %s:%d UID(%d) user %s", ++ originator, originator_port, target, target_port, user, ++ getpwuid(user)->pw_name); ++ + debug_f("originator %s port %u, target %s port %u", + originator, originator_port, target, target_port); + diff --git a/fb87_log_session_id.patch b/fb87_log_session_id.patch new file mode 100644 index 0000000000000000000000000000000000000000..cb120d0163681768929c48011cc9c988aa88c0dc --- /dev/null +++ b/fb87_log_session_id.patch @@ -0,0 +1,142 @@ +Index: openssh-9.9p1/sshd.c +=================================================================== +--- openssh-9.9p1.orig/sshd.c ++++ openssh-9.9p1/sshd.c +@@ -1768,6 +1768,9 @@ main(int ac, char **av) + /* Accept a connection and return in a forked child */ + server_accept_loop(&sock_in, &sock_out, + &newsock, config_s, log_stderr); ++ ++ set_log_session_id(); // Set log session ID for this session ++ + } + + /* This is the child processing a new connection. */ +@@ -1818,3 +1821,4 @@ cleanup_exit(int i) + { + _exit(i); + } ++ +Index: openssh-9.9p1/log.c +=================================================================== +--- openssh-9.9p1.orig/log.c ++++ openssh-9.9p1/log.c +@@ -414,25 +414,52 @@ do_log(LogLevel level, int force, const + tmp_handler(level, force, fmtbuf, log_handler_ctx); + log_handler = tmp_handler; + } else if (log_on_stderr) { +- snprintf(msgbuf, sizeof msgbuf, "%s%s%.*s\r\n", ++ snprintf(msgbuf, sizeof msgbuf, "%s%s%.*s session=%s\r\n", + (log_on_stderr > 1) ? progname : "", + (log_on_stderr > 1) ? ": " : "", +- (int)sizeof msgbuf - 3, fmtbuf); ++ (int)sizeof msgbuf - 3, fmtbuf, get_log_session_id()); + (void)write(log_stderr_fd, msgbuf, strlen(msgbuf)); + } else { + #if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT) + openlog_r(progname, LOG_PID, log_facility, &sdata); +- syslog_r(pri, &sdata, "%.500s", fmtbuf); ++ syslog_r(pri, &sdata, "%.500s session=%s", fmtbuf, get_log_session_id()); + closelog_r(&sdata); + #else + openlog(progname, LOG_PID, log_facility); +- syslog(pri, "%.500s", fmtbuf); ++ syslog(pri, "%.500s session=%s", fmtbuf, get_log_session_id()); + closelog(); + #endif + } + errno = saved_errno; + } + ++void ++set_log_session_id() ++{ ++ struct timeval tv; ++ char hostname[HOST_NAME_MAX + 1]; ++ char session_id[HOST_NAME_MAX + 20]; ++ char *s; ++ if (gethostname(hostname, sizeof(hostname)) != 0) { ++ *hostname = '\0'; ++ } ++ gettimeofday(&tv, NULL); ++ snprintf(session_id, sizeof(session_id), "%s:%x.%x", ++ hostname, tv.tv_sec, tv.tv_usec); ++ setenv("LOG_SESSION_ID", session_id, 1); ++} ++ ++const char * ++get_log_session_id() ++{ ++ const char *id = getenv("LOG_SESSION_ID"); ++ if (!id) { ++ set_log_session_id(); ++ id = getenv("LOG_SESSION_ID"); ++ } ++ return id; ++} ++ + void + sshlog(const char *file, const char *func, int line, int showfunc, + LogLevel level, const char *suffix, const char *fmt, ...) +@@ -519,3 +546,4 @@ sshlogdirect(LogLevel level, int forced, + do_log(level, forced, NULL, fmt, args); + va_end(args); + } ++ +Index: openssh-9.9p1/regress/session-id.sh +=================================================================== +--- /dev/null ++++ openssh-9.9p1/regress/session-id.sh +@@ -0,0 +1,23 @@ ++tid="session id" ++ ++start_sshd ++ ++${SSH} -F $OBJ/ssh_config somehost true ++if [ $? -ne 0 ]; then ++ fail "ssh connect with failed" ++fi ++ ++expected="session=$(hostname)" ++ ++# grab the first session ID which will be stable across session ++sessionid=$(grep -m1 $expected $TEST_SSHD_LOGFILE | sed -E 's/.*(session=.*)/\1/') ++ ++line_count=$(grep -c $expected $TEST_SSHD_LOGFILE) ++if [ $line_count == "0" ]; then ++ fail "No session ID lines found" ++fi ++ ++stable_id_count=$(grep -c $sessionid $TEST_SSHD_LOGFILE) ++if [ $line_count != $stable_id_count ]; then ++ fail 'Mismatching session ids found' ++fi +Index: openssh-9.9p1/log.h +=================================================================== +--- openssh-9.9p1.orig/log.h ++++ openssh-9.9p1/log.h +@@ -68,6 +68,9 @@ const char * log_level_name(LogLevel); + void set_log_handler(log_handler_fn *, void *); + void cleanup_exit(int) __attribute__((noreturn)); + ++void set_log_session_id(); ++const char * get_log_session_id(); ++ + void sshlog(const char *, const char *, int, int, + LogLevel, const char *, const char *, ...) + __attribute__((format(printf, 7, 8))); +Index: openssh-9.9p1/session.c +=================================================================== +--- openssh-9.9p1.orig/session.c ++++ openssh-9.9p1/session.c +@@ -1242,6 +1242,10 @@ do_setup_env(struct ssh *ssh, Session *s + child_set_env(&env, &envsize, "SSH_ORIGINAL_COMMAND", + original_command); + ++ /* set LOG_SESSION_ID for child */ ++ child_set_env(&env, &envsize, "LOG_SESSION_ID", get_log_session_id()); ++ debug("set LOG_SESION_ID to: %s", get_log_session_id()); ++ + if (debug_flag) { + /* dump the environment */ + fprintf(stderr, "Environment:\n"); diff --git a/fb87_pass_principals_to_child.patch b/fb87_pass_principals_to_child.patch new file mode 100644 index 0000000000000000000000000000000000000000..49de966dd4047469c3d78e4f3ef289b284f562c9 --- /dev/null +++ b/fb87_pass_principals_to_child.patch @@ -0,0 +1,221 @@ +--- a/session.c ++++ b/session.c +@@ -98,6 +98,7 @@ + #include "atomicio.h" + #include "slog.h" + ++#define SSH_MAX_PUBKEY_BYTES 16384 + + #if defined(KRB5) && defined(USE_AFS) + #include +@@ -1054,11 +1055,18 @@ copy_environment(char **source, char *** + static char ** + do_setup_env(struct ssh *ssh, Session *s, const char *shell) + { +- char buf[256]; ++ char buf[SSH_MAX_PUBKEY_BYTES]; ++ char *pbuf = &buf[0]; + size_t n; + u_int i, envsize; + char *ocp, *cp, *value, **env, *laddr; + struct passwd *pw = s->pw; ++ Authctxt *authctxt = s->authctxt; ++ struct sshkey *key; ++ size_t len = 0; ++ ssize_t total = 0; ++ struct sshkey_cert *cert; ++ + #if !defined (HAVE_LOGIN_CAP) && !defined (HAVE_CYGWIN) + char *path = NULL; + #endif +@@ -1255,9 +1263,57 @@ do_setup_env(struct ssh *ssh, Session *s + child_set_env(&env, &envsize, "SSH_USER_AUTH", auth_info_file); + if (s->ttyfd != -1) + child_set_env(&env, &envsize, "SSH_TTY", s->tty); +- if (original_command) ++ if (original_command) { + child_set_env(&env, &envsize, "SSH_ORIGINAL_COMMAND", + original_command); ++ /* ++ * Set SSH_CERT_PRINCIPALS to be the principals on the ssh certificate. ++ * Only do so when a force command is present to prevent the client ++ * from changing the value of SSH_CERT_PRINCIPALS. For example, when a ++ * client is given shell access, the client can easily change the ++ * value of an environment variable by running, e.g., ++ * ssh user@host.address 'SSH_CERT_PRINCIPALS=attacker env' ++ */ ++ ++ if (authctxt->nprev_keys > 0) { ++ key = authctxt->prev_keys[authctxt->nprev_keys-1]; ++ /* If a user was authorized by a certificate, set SSH_CERT_PRINCIPALS */ ++ if (sshkey_is_cert(key)) { ++ cert = key->cert; ++ ++ for (i = 0; i < cert->nprincipals - 1; ++i) { ++ /* ++ * total: bytes written to buf so far ++ * 2: one for comma and one for '\0' to be added by snprintf ++ * We stop at the first principal overflowing buf. ++ */ ++ if (total + strlen(cert->principals[i]) + 2 > SSH_MAX_PUBKEY_BYTES) ++ break; ++ ++ len = snprintf(pbuf, SSH_MAX_PUBKEY_BYTES-total, "%s,", ++ cert->principals[i]); ++ /* pbuf advances by len, the '\0' at the end will be overwritten */ ++ pbuf += len; ++ total += len; ++ } ++ ++ if (total + strlen(cert->principals[i]) + 1 <= SSH_MAX_PUBKEY_BYTES) { ++ len = snprintf(pbuf, SSH_MAX_PUBKEY_BYTES-total, "%s", ++ cert->principals[i]); ++ total += len; ++ } else if (total > 0) ++ /* ++ * If we hit the overflow condition, remove the trailing comma. ++ * We only do so if the overflowing principal is not the first one on the ++ * certificate so that there is at least one principal in buf ++ */ ++ buf[total-1] = '\0'; ++ ++ if (total > 0) ++ child_set_env(&env, &envsize, "SSH_CERT_PRINCIPALS", buf); ++ } ++ } ++ } + + /* set LOG_SESSION_ID for child */ + child_set_env(&env, &envsize, "LOG_SESSION_ID", get_log_session_id()); +--- /dev/null ++++ b/regress/cert-princ-env.sh +@@ -0,0 +1,129 @@ ++tid="cert principal env" ++ ++# change to ecdsa ++CERT_ID="$USER" ++AUTH_PRINC_FILE="$OBJ/auth_principals" ++CA_FILE="$OBJ/ca-rsa" ++IDENTITY_FILE="$OBJ/$USER-rsa" ++SSH_MAX_PUBKEY_BYTES=16384 ++ ++cat << EOF >> $OBJ/sshd_config ++TrustedUserCAKeys $CA_FILE.pub ++Protocol 2 ++PubkeyAuthentication yes ++AuthenticationMethods publickey ++AuthorizedPrincipalsFile $AUTH_PRINC_FILE ++ForceCommand=/bin/env ++EOF ++ ++cleanup() { ++ rm -f $CA_FILE{.pub,} ++ rm -f $IDENTITY_FILE{-cert.pub,.pub,} ++ rm -f $AUTH_PRINC_FILE ++} ++ ++make_keys_and_certs() { ++ rm -f $CA_FILE{.pub,} ++ rm -f $IDENTITY_FILE{-cert.pub,.pub,} ++ ++ local princs=$1 ++ ++ ${SSHKEYGEN} -q -t rsa -C '' -N '' -f $CA_FILE || ++ fatal 'Could not create CA key' ++ ++ ${SSHKEYGEN} -q -t rsa -C '' -N '' -f $IDENTITY_FILE || ++ fatal 'Could not create keypair' ++ ++ ${SSHKEYGEN} -q -s $CA_FILE -I $CERT_ID -n "$princs" -z "42" "$IDENTITY_FILE.pub" || ++ fatal "Could not create SSH cert" ++} ++ ++test_with_expected_principals() { ++ local princs=$1 ++ ++ out=$(${SSH} -E thlog -F $OBJ/ssh_config -i "$IDENTITY_FILE" somehost false) || ++ fatal "SSH failed" ++ ++ echo "$out" | grep -q "SSH_CERT_PRINCIPALS=$princs$" || ++ fatal "SSH_CERT_PRINCIPALS has incorrect value" ++} ++ ++test_with_no_expected_principals() { ++ local princs=$1 ++ ++ out=$(${SSH} -E thlog -F $OBJ/ssh_config -i "$IDENTITY_FILE" somehost false) || ++ fatal "SSH failed" ++ ++ echo "$out" | grep -vq "SSH_CERT_PRINCIPALS" || ++ fatal "SSH_CERT_PRINCIPALS env should not be set" ++ ++ echo "$out" | grep -vq "SSH_CERT_PRINCIPALS=$princs" || ++ fatal "SSH_CERT_PRINCIPALS has incorrect value" ++} ++ ++ ++echo 'a' > $AUTH_PRINC_FILE ++start_sshd ++ ++principals="a,b,c,d" ++make_keys_and_certs "$principals" ++test_with_expected_principals "$principals" ++ ++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16381 | head -n 1) ++make_keys_and_certs "a,$big_princ" ++test_with_expected_principals "a,$big_princ" ++ ++# No room for two principals ++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16382 | head -n 1) ++make_keys_and_certs "a,$big_princ" ++test_with_expected_principals "a" ++ ++make_keys_and_certs "$big_princ,a" ++test_with_expected_principals "$big_princ" ++ ++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16384 | head -n 1) ++make_keys_and_certs "a,$big_princ" ++test_with_expected_principals "a" ++ ++# principal too big for buffer ++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $SSH_MAX_PUBKEY_BYTES | head -n 1) ++make_keys_and_certs "$big_princ" ++test_with_no_expected_principals "$big_princ" ++ ++# no matching principals in certificate and auth princ file ++principals="b,c,d" ++make_keys_and_certs "$principals" ++test_with_no_expected_principals "$principals" ++ ++stop_sshd ++ ++cat << EOF >> $OBJ/sshd_config ++TrustedUserCAKeys $CA_FILE.pub ++Protocol 2 ++PubkeyAuthentication yes ++AuthenticationMethods publickey ++AuthorizedPrincipalsFile $AUTH_PRINC_FILE ++EOF ++ ++start_sshd ++ ++# no force command, no princpals ++principals="a,b,c,d" ++make_keys_and_certs "$principals" ++test_with_no_expected_principals "$principals" ++ ++stop_sshd ++ ++cat << EOF >> $OBJ/sshd_config ++Protocol 2 ++PubkeyAuthentication yes ++AuthenticationMethods publickey ++AuthorizedPrincipalsFile $AUTH_PRINC_FILE ++EOF ++ ++start_sshd ++ ++# No TrustedUserCAKeys causes pubkey auth, no principals ++principals="a,b,c,d" ++make_keys_and_certs "$principals" ++test_with_no_expected_principals "$principals" diff --git a/fb87_set_sock_tos.patch b/fb87_set_sock_tos.patch new file mode 100644 index 0000000000000000000000000000000000000000..4920d747560dc5b191ad40396c31da6bddf652b3 --- /dev/null +++ b/fb87_set_sock_tos.patch @@ -0,0 +1,18 @@ +Index: openssh-9.9p1/sshd.c +=================================================================== +--- openssh-9.9p1.orig/sshd.c ++++ openssh-9.9p1/sshd.c +@@ -811,6 +811,13 @@ listen_on_addrs(struct listenaddr *la) + if (ai->ai_family == AF_INET6) + sock_set_v6only(listen_sock); + ++ /* ++ * Set the default TOS for the socket to interactive. ++ * It can be downgraded later to the bulk QOS if necesary in do_exec ++ */ ++ if (options.ip_qos_interactive != INT_MAX) ++ set_sock_tos(listen_sock, options.ip_qos_interactive); ++ + debug("Bind to port %s on %s.", strport, ntop); + + /* Bind the socket to the desired port. */ diff --git a/fb87_sk_ecdsa_webauthn.patch b/fb87_sk_ecdsa_webauthn.patch new file mode 100644 index 0000000000000000000000000000000000000000..8bbc7a40e01a1a82fc4d1f51e59e80388eb16a6b --- /dev/null +++ b/fb87_sk_ecdsa_webauthn.patch @@ -0,0 +1,30 @@ +Index: openssh-8.7p1/ssh-ecdsa-sk.c +=================================================================== +--- openssh-8.7p1.orig/ssh-ecdsa-sk.c ++++ openssh-8.7p1/ssh-ecdsa-sk.c +@@ -191,14 +191,17 @@ ssh_ecdsa_sk_verify(const struct sshkey + ret = SSH_ERR_INVALID_FORMAT; + goto out; + } +- if (is_webauthn) { +- if (sshbuf_get_cstring(b, &webauthn_origin, NULL) != 0 || +- sshbuf_froms(b, &webauthn_wrapper) != 0 || +- sshbuf_froms(b, &webauthn_exts) != 0) { +- ret = SSH_ERR_INVALID_FORMAT; +- goto out; +- } +- } ++ if (sshbuf_get_cstring(b, &webauthn_origin, NULL) != 0 || ++ sshbuf_froms(b, &webauthn_wrapper) != 0 || ++ sshbuf_froms(b, &webauthn_exts) != 0) { ++ if (is_webauthn) { ++ ret = SSH_ERR_INVALID_FORMAT; ++ goto out; ++ } ++ } else { ++ // webauthn signature detected based on structure ++ is_webauthn = 1; ++ } + if (sshbuf_len(b) != 0) { + ret = SSH_ERR_UNEXPECTED_TRAILING_DATA; + goto out; diff --git a/fb87_slog.patch b/fb87_slog.patch new file mode 100644 index 0000000000000000000000000000000000000000..01f68b0d27d01b207f00348f56971afb2dbe7f59 --- /dev/null +++ b/fb87_slog.patch @@ -0,0 +1,1167 @@ +Index: openssh-9.9p1/slog.c +=================================================================== +--- /dev/null ++++ openssh-9.9p1/slog.c +@@ -0,0 +1,619 @@ ++/* ++ * Copyright 2004-present Facebook. All Rights Reserved. ++ */ ++ ++ /* When using slogctxt in any module perform a NULL pointer check */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "includes.h" ++#include "slog.h" ++#include "log.h" ++#include "misc.h" ++#include "servconf.h" ++#include "xmalloc.h" ++ ++typedef struct Structuredlogctxt Structuredlogctxt; ++ ++struct Structuredlogctxt { /* items we care about logging */ ++ char server_ip[SLOG_SHORT_STRING_LEN]; // server_ip ++ int server_port; // server_port ++ char remote_ip[SLOG_SHORT_STRING_LEN]; // remote_ip ++ int remote_port; // remote_port ++ pid_t pam_process_pid; // pam_pid ++ char session_id[SLOG_STRING_LEN]; // session_id ++ char method[SLOG_STRING_LEN]; // method ++ char cert_id[SLOG_STRING_LEN]; // cert_id ++ unsigned long long cert_serial; // cert_serial ++ char principal[SLOG_STRING_LEN]; // principal ++ char user[SLOG_STRING_LEN]; // user ++ char command[SLOG_LONG_STRING_LEN]; // command ++ SLOG_SESSION_STATE session_state; // session_state ++ SLOG_AUTHENTICATED auth_successful; // auth_successful ++ time_t start_time; // start_time ++ time_t end_time; // end_time ++ int duration; // duration ++ pid_t main_pid; // main_pid ++ char auth_info[SLOG_MEDIUM_STRING_LEN]; // auth_info ++ char client_version[SLOG_STRING_LEN]; // client_version ++}; ++ ++Structuredlogctxt *slogctxt; ++extern ServerOptions options; ++ ++// Define token constants and default syntax format ++static const char *server_ip_token = "server_ip"; ++static const char *server_port_token = "server_port"; ++static const char *remote_ip_token = "remote_ip"; ++static const char *remote_port_token = "remote_port"; ++static const char *pam_pid_token = "pam_pid"; ++static const char *process_pid_token = "pid"; ++static const char *session_id_token = "session_id"; ++static const char *method_token = "method"; ++static const char *cert_id_token = "cert_id"; ++static const char *cert_serial_token = "cert_serial"; ++static const char *principal_token = "principal"; ++static const char *user_token = "user"; ++static const char *command_token = "command"; ++static const char *session_state_token = "session_state"; ++static const char *auth_successful_token = "auth_successful"; ++static const char *start_time_token = "start_time"; ++static const char *end_time_token = "end_time"; ++static const char *duration_token = "duration"; ++static const char *main_pid_token = "main_pid"; ++static const char *auth_info_token = "auth_info"; ++static const char *client_version = "client_version"; ++ ++/* Example log format sshd_config ++ * LogFormatPrefix sshd_auth_msg: ++ * LogFormatKeys server_ip server_port remote_ip remote_port pid session_id / ++ method cert_id cert_serial principal user session_state auth_successful / ++ start_time command # NO LINE BREAKS ++ * LogFormatJson yes # no makes this output an json array vs json object ++ */ ++ ++// Set a arbitrary large size so we can feed a potentially ++// large json object to the logger ++#define SLOG_BUF_SIZE 8192 ++#define SLOG_TRUNCATED_MESSAGE_JSON ", \"incomplete\": \"true\"}" ++#define SLOG_TRUNCATED_MESSAGE_ARRAY ", \"incomplete\"]" ++#define SLOG_TRUNCATED_SIZE 25 ++/* size of format for JSON for quotes_colon_space around key and comma_space ++ after value or closure_null */ ++#define SLOG_JSON_FORMAT_SIZE 6 ++#define SLOG_BUF_CALC_SIZE SLOG_BUF_SIZE - SLOG_TRUNCATED_SIZE ++ ++/* private declarations */ ++static void slog_log(void); ++static void slog_cleanup(void); ++static void slog_generate_auth_payload(char *); ++static void slog_escape_value(char *, char *, size_t); ++static void slog_get_safe_from_token(char *, const char *); ++static const char* slog_get_state_text(void); ++ ++/* public functions */ ++ ++void ++slog_init(void) ++{ ++ /* initialize only if we have log_format_keys */ ++ if (options.num_log_format_keys != 0) { ++ slogctxt = xcalloc(1, sizeof(Structuredlogctxt)); ++ if (slogctxt != NULL) ++ slog_cleanup(); ++ } ++} ++ ++void ++slog_pam_session_opened(void) ++{ ++ if (slogctxt != NULL) { ++ slogctxt->session_state = SLOG_SESSION_OPEN; ++ slogctxt->pam_process_pid = getpid(); ++ } ++} ++ ++void ++slog_set_auth_data(int authenticated, const char *method, const char *user) ++{ ++ if (slogctxt != NULL) { ++ slogctxt->auth_successful = ++ authenticated ? SLOG_AUTHORIZED : SLOG_UNAUTHORIZED; ++ strlcpy(slogctxt->method, method, SLOG_SHORT_STRING_LEN); ++ strlcpy(slogctxt->user, user, SLOG_STRING_LEN); ++ } ++} ++ ++void ++slog_set_cert_id(const char *id) ++{ ++ if (slogctxt != NULL) ++ strlcpy(slogctxt->cert_id, id, SLOG_STRING_LEN); ++} ++ ++ ++void ++slog_set_cert_serial(unsigned long long serial) ++{ ++ if (slogctxt != NULL) ++ slogctxt->cert_serial = serial; ++} ++ ++void ++slog_set_connection(const char *remote_ip, int remote_port, ++ const char *server_ip, int server_port, const char *session) ++{ ++ if (slogctxt != NULL) { ++ strlcpy(slogctxt->remote_ip, remote_ip, SLOG_SHORT_STRING_LEN); ++ slogctxt->remote_port = remote_port; ++ strlcpy(slogctxt->server_ip, server_ip, SLOG_SHORT_STRING_LEN); ++ slogctxt->server_port = server_port; ++ strlcpy(slogctxt->session_id, session, SLOG_STRING_LEN); ++ slogctxt->start_time = time(NULL); ++ slogctxt->main_pid = getpid(); ++ } ++} ++ ++void ++slog_set_client_version(const char *version) ++{ ++ if (slogctxt != NULL) { ++ if (strlen(version) < SLOG_STRING_LEN) ++ strlcpy(slogctxt->client_version, version, SLOG_STRING_LEN); ++ else { ++ // version can be up to 256 bytes, truncate to 95 and add ' ...' ++ // which will fit in SLOG_STRING_LEN ++ snprintf(slogctxt->client_version, SLOG_STRING_LEN, "%.95s ...", version); ++ } ++ } ++} ++ ++void ++slog_set_command(const char *command) ++{ ++ if (slogctxt != NULL) { ++ if (strlen(command) < SLOG_LONG_STRING_LEN) ++ strlcpy(slogctxt->command, command, SLOG_LONG_STRING_LEN); ++ else { ++ // If command is longer than allowed we truncate it to ++ // 1995 (SLOG_LONG_STRING_LEN - 5) characters and add ' ...\0' to ++ // the end of the command. ++ snprintf(slogctxt->command, SLOG_LONG_STRING_LEN, "%.1995s ...", command); ++ } ++ } ++} ++ ++void ++slog_set_principal(const char *principal) ++{ ++ if (slogctxt != NULL) ++ strlcpy(slogctxt->principal, principal, SLOG_STRING_LEN); ++} ++ ++void ++slog_set_user(const char *user) ++{ ++ if (slogctxt != NULL) ++ strlcpy(slogctxt->user, user, SLOG_STRING_LEN); ++} ++ ++void ++slog_set_auth_info(const char *auth_info) ++{ ++ if (slogctxt != NULL) ++ strlcpy(slogctxt->auth_info, auth_info, SLOG_MEDIUM_STRING_LEN); ++} ++ ++void ++slog_exit_handler(void) ++{ ++ /* to prevent duplicate logging we only log based on the pid set */ ++ if (slogctxt != NULL) { ++ if (slogctxt->server_ip[0] == 0) ++ return; // not initialized ++ if (slogctxt->main_pid != getpid()) ++ return; // not main process ++ if (slogctxt->session_state == SLOG_SESSION_INIT) ++ slog_log(); ++ else { ++ slogctxt->session_state = SLOG_SESSION_CLOSED; ++ slogctxt->end_time = time(NULL); ++ slogctxt->duration = slogctxt->end_time - slogctxt->start_time; ++ slog_log(); ++ slog_cleanup(); ++ } ++ } ++} ++ ++void ++slog_log_session(void) ++{ ++ if (slogctxt != NULL) { ++ slogctxt->session_state = SLOG_SESSION_OPEN; ++ slog_log(); ++ } ++} ++ ++/* private function scope begin */ ++ ++static void ++slog_log(void) ++{ ++ char *buffer = xmalloc(SLOG_BUF_SIZE); ++ ++ if (buffer == NULL) ++ return; ++ ++ memset(buffer, 0, SLOG_BUF_SIZE); ++ ++ if (options.num_log_format_keys > 0 ++ && slogctxt != NULL ++ && slogctxt->server_ip[0] != 0 ++ && slogctxt->user[0] != 0) { ++ slog_generate_auth_payload(buffer); ++ do_log_slog_payload(buffer); ++ } ++ ++ free(buffer); ++} ++ ++static void ++slog_cleanup(void) ++{ ++ // Reset the log context values ++ if (slogctxt != NULL) { ++ memset(slogctxt, 0, sizeof(Structuredlogctxt)); ++ slogctxt->session_state = SLOG_SESSION_INIT; ++ slogctxt->auth_successful = SLOG_UNAUTHORIZED; ++ } ++} ++ ++/* We use debug3 since the debug is very noisy */ ++static void ++slog_generate_auth_payload(char *buf) ++{ ++ if (buf == NULL) ++ return; ++ ++ // Create large buffer so don't risk overflow ++ char *safe = xmalloc(SLOG_BUF_SIZE); ++ memset(safe, 0, SLOG_BUF_SIZE); ++ ++ if (safe == NULL) ++ return; ++ ++ int i; ++ size_t remaining; ++ int json = options.log_format_json; ++ int keys = options.num_log_format_keys; ++ int truncated = 0; ++ char *tmpbuf = buf; ++ char *key; ++ ++ debug3("JSON format is %d with %d tokens.", json, keys); ++ ++ if (options.log_format_prefix != NULL ++ && strlen(options.log_format_prefix) < SLOG_BUF_CALC_SIZE-1) { ++ tmpbuf += snprintf(tmpbuf, SLOG_BUF_CALC_SIZE, ++ "%s ", options.log_format_prefix); ++ } ++ *tmpbuf++ = (json) ? '{' : '['; ++ debug3("current buffer after prefix: %s", buf); ++ ++ // Loop through the keys filling out the output string ++ for (i = 0; i < keys; i++) { ++ safe[0] = 0; // clear safe string ++ key = options.log_format_keys[i]; ++ remaining = SLOG_BUF_CALC_SIZE - (tmpbuf - buf); ++ ++ if (key == NULL) ++ continue; // Shouldn't happen but if null go to next key ++ ++ slog_get_safe_from_token(safe, key); ++ debug3("token: %s, value: %s", key, safe); ++ ++ if (json) { ++ if (*safe == '\0') ++ continue; // No value since we are using key pairs skip ++ if (remaining <= SLOG_JSON_FORMAT_SIZE + strlen(key) + strlen(safe)) { ++ debug("Log would exceed buffer size %u, %zu, %zu at key: %s", ++ (unsigned int)remaining, strlen(key), strlen(safe), key); ++ truncated = 1; ++ break; ++ } ++ tmpbuf += snprintf(tmpbuf, remaining, "%s\"%s\": %s", ++ i > 0 ? ", " : "", key, safe); ++ } else { ++ if (*safe == '\0') ++ strlcpy(safe, "\"\"", SLOG_SHORT_STRING_LEN); ++ if (remaining < SLOG_JSON_FORMAT_SIZE + strlen(safe)) { ++ debug("Log would exceed remaining buffer size %d, %zu, at key: %s", ++ (unsigned int)remaining, strlen(safe), key); ++ truncated = 1; ++ break; ++ } ++ tmpbuf += snprintf(tmpbuf, remaining, "%s%s", i > 0 ? ", " : "", safe); ++ } ++ debug3("current buffer after token: %s", buf); ++ debug3("end of loop key: %s, %d out of %d keys", key, i + 1, keys); ++ } ++ ++ // Close the log string. If truncated set truncated message and close string ++ if (truncated == 1) ++ strlcpy(tmpbuf, json ? SLOG_TRUNCATED_MESSAGE_JSON : ++ SLOG_TRUNCATED_MESSAGE_ARRAY, SLOG_TRUNCATED_SIZE); ++ else { ++ *tmpbuf++ = (json) ? '}' : ']'; ++ } ++ ++ free(safe); ++} ++ ++// buffer size is input string * 2 +1 ++static void ++slog_escape_value(char *output, char *input, size_t buffer_size) ++{ ++ int i; ++ buffer_size -= 2; ++ if (input != NULL) { ++ int input_size = strlen(input); ++ char *temp = output; ++ *temp++ = '"'; ++ buffer_size--; ++ for (i = 0; i < input_size && buffer_size > 0; i++) { ++ switch(input[i]) { ++ // characters escaped are the same as folly::json::escapeString ++ case 27: // ascii control character ++ if (buffer_size > 6) { ++ *temp++ = '\\'; ++ *temp++ = 'u'; ++ *temp++ = '0'; ++ *temp++ = '0'; ++ *temp++ = '1'; ++ *temp++ = 'b'; ++ buffer_size -= 6; ++ } ++ case '\b': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ *temp++ = 'b'; ++ buffer_size -= 2; ++ } ++ break; ++ case '\f': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ *temp++ = 'f'; ++ buffer_size -= 2; ++ } ++ break; ++ case '\n': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ *temp++ = 'n'; ++ buffer_size -= 2; ++ } ++ break; ++ case '\r': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ *temp++ = 'r'; ++ buffer_size -= 2; ++ } ++ break; ++ case '\t': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ *temp++ = 't'; ++ buffer_size -= 2; ++ } ++ break; ++ case '\"': ++ case '\\': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ buffer_size--; ++ } ++ default: // Non-escape char ++ *temp++ = input[i]; ++ buffer_size--; ++ } ++ } ++ *temp++ = '"'; ++ *temp++ = '\0'; ++ } ++} ++ ++static void ++slog_get_safe_from_token(char *output, const char *token) ++{ ++ if (output == NULL || token == NULL || slogctxt == NULL) ++ return; ++ ++ if (strcmp(token, server_ip_token) == 0) { ++ if (slogctxt->server_ip[0] != 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"", ++ slogctxt->server_ip); ++ } ++ return; ++ } ++ ++ if (strcmp(token, server_port_token) == 0) { ++ if (slogctxt->server_port > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", ++ slogctxt->server_port); ++ } ++ return; ++ } ++ ++ if (strcmp(token, remote_ip_token) == 0) { ++ if (slogctxt->remote_ip[0] != 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"", ++ slogctxt->remote_ip); ++ } ++ return; ++ } ++ ++ if (strcmp(token, remote_port_token) == 0) { ++ if (slogctxt->remote_port > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", ++ slogctxt->remote_port); ++ } ++ return; ++ } ++ ++ if (strcmp(token, pam_pid_token) == 0) { ++ if (slogctxt->pam_process_pid > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"", ++ (long)slogctxt->pam_process_pid); ++ } ++ return; ++ } ++ ++ if (strcmp(token, process_pid_token) == 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"", (long)getpid()); ++ return; ++ } ++ ++ if (strcmp(token, session_id_token) == 0) { ++ if (slogctxt->session_id[0] != 0) { ++ snprintf(output, SLOG_STRING_LEN, "\"%s\"", ++ slogctxt->session_id); ++ } ++ return; ++ } ++ ++ if (strcmp(token, method_token) == 0) { ++ if (slogctxt->method[0] != 0) { ++ snprintf(output, SLOG_STRING_LEN, "\"%s\"", ++ slogctxt->method); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strcmp(token, cert_id_token) == 0) { ++ if (slogctxt->cert_id[0] != 0 && ++ strcmp(slogctxt->method, "publickey") == 0) { ++ slog_escape_value(output, slogctxt->cert_id, ++ SLOG_STRING_LEN * 2 + 1); ++ } ++ return; ++ } ++ ++ if (strcmp(token, cert_serial_token) == 0) { ++ if (slogctxt->cert_serial > 0 && ++ strcmp(slogctxt->method, "publickey") == 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%llu\"", ++ slogctxt->cert_serial); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strcmp(token, principal_token) == 0) { ++ if (slogctxt->principal[0] != 0) { ++ slog_escape_value(output, slogctxt->principal, ++ SLOG_STRING_LEN * 2 + 1); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strcmp(token, user_token) == 0) { ++ if (slogctxt->user[0] != 0) { ++ slog_escape_value(output, slogctxt->user, ++ SLOG_STRING_LEN * 2 + 1); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strcmp(token, auth_info_token) == 0) { ++ if (slogctxt->auth_info[0] != 0) { ++ slog_escape_value(output, slogctxt->auth_info, ++ SLOG_MEDIUM_STRING_LEN * 2 + 1); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strcmp(token, command_token) == 0) { ++ if (slogctxt->command[0] != 0) { ++ slog_escape_value(output, slogctxt->command, ++ SLOG_LONG_STRING_LEN * 2 + 1); ++ } ++ return; ++ } ++ ++ if (strcmp(token, auth_successful_token) == 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"", ++ slogctxt->auth_successful == SLOG_AUTHORIZED ? "true" : "false"); ++ return; ++ } ++ ++ if (strcmp(token, session_state_token) == 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"", ++ slog_get_state_text()); ++ return; ++ } ++ ++ if (strcmp(token, start_time_token) == 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", ++ (int)slogctxt->start_time); ++ return; ++ } ++ ++ if (strcmp(token, end_time_token) == 0 && slogctxt->end_time > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", ++ (int)slogctxt->end_time); ++ return; ++ } ++ ++ if (strcmp(token, duration_token) == 0 && slogctxt->end_time > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", slogctxt->duration); ++ return; ++ } ++ ++ if (strcmp(token, main_pid_token) == 0) { ++ if (slogctxt->main_pid > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"", ++ (long)slogctxt->main_pid); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strncmp(token, client_version, strlen(client_version)) == 0) { ++ if (slogctxt->client_version[0] != 0) { ++ slog_escape_value(output, slogctxt->client_version, ++ SLOG_STRING_LEN + 2); ++ } ++ return; ++ } ++} ++ ++static const char * ++slog_get_state_text(void) ++{ ++ if (slogctxt == NULL) ++ return ""; ++ ++ switch (slogctxt->session_state) { ++ case SLOG_SESSION_INIT: ++ return "Session failed"; ++ case SLOG_SESSION_OPEN: ++ return "Session opened"; ++ case SLOG_SESSION_CLOSED: ++ return "Session closed"; ++ default: ++ return "Unknown session state"; // Should never happen ++ } ++} +Index: openssh-9.9p1/servconf.c +=================================================================== +--- openssh-9.9p1.orig/servconf.c ++++ openssh-9.9p1/servconf.c +@@ -219,6 +219,9 @@ initialize_server_options(ServerOptions + options->disable_forwarding = -1; + options->expose_userauth_info = -1; + options->required_rsa_size = -1; ++ options->log_format_prefix = NULL; ++ options->num_log_format_keys = 0; ++ options->log_format_json = -1; + options->channel_timeouts = NULL; + options->num_channel_timeouts = 0; + options->unused_connection_timeout = -1; +@@ -519,6 +522,8 @@ fill_default_server_options(ServerOption + options->sk_provider = xstrdup("internal"); + if (options->required_rsa_size == -1) + options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE; ++ if (options->log_format_json == -1) ++ options->log_format_json = 0; + if (options->unused_connection_timeout == -1) + options->unused_connection_timeout = 0; + if (options->sshd_session_path == NULL) +@@ -609,6 +614,9 @@ typedef enum { + sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, + sRequiredRSASize, sChannelTimeout, sUnusedConnectionTimeout, + sSshdSessionPath, sRefuseConnection, ++ /* Structured Logging options. Unless sLogFormatKeys is set, ++ structured logging is disabled */ ++ sLogFormatPrefix, sLogFormatKeys, sLogFormatJson, + sDeprecated, sIgnore, sUnsupported + } ServerOpCodes; + +@@ -791,6 +799,9 @@ static struct { + { "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL }, + { "requiredrsasize", sRequiredRSASize, SSHCFG_ALL }, + { "rsaminsize", sRequiredRSASize, SSHCFG_ALL }, /* alias */ ++ { "logformatprefix", sLogFormatPrefix, SSHCFG_GLOBAL }, ++ { "logformatkeys", sLogFormatKeys, SSHCFG_GLOBAL }, ++ { "logformatjson", sLogFormatJson, SSHCFG_GLOBAL }, + { "channeltimeout", sChannelTimeout, SSHCFG_ALL }, + { "unusedconnectiontimeout", sUnusedConnectionTimeout, SSHCFG_ALL }, + { "sshdsessionpath", sSshdSessionPath, SSHCFG_GLOBAL }, +@@ -2552,6 +2563,30 @@ process_server_config_line_depth(ServerO + } + break; + ++ case sLogFormatPrefix: ++ arg = argv_next(&ac, &av); ++ if (!arg || *arg == '\0') { ++ fatal("%.200s line %d: invalid log format prefix", ++ filename, linenum); ++ } ++ options->log_format_prefix = xstrdup(arg); ++ break; ++ ++ case sLogFormatKeys: ++ while ((arg = argv_next(&ac, &av)) && *arg != '\0') { ++ if (options->num_log_format_keys >= MAX_LOGFORMAT_KEYS) ++ fatal("%s line %d: too long format keys.", ++ filename, linenum); ++ if (!*activep) ++ continue; ++ options->log_format_keys[options->num_log_format_keys++] = xstrdup(arg); ++ } ++ break; ++ ++ case sLogFormatJson: ++ intptr = &options->log_format_json; ++ goto parse_flag; ++ + case sIPQoS: + arg = argv_next(&ac, &av); + if (!arg || *arg == '\0') +@@ -3326,6 +3361,7 @@ dump_config(ServerOptions *o) + dump_cfg_fmtint(sStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink); + dump_cfg_fmtint(sFingerprintHash, o->fingerprint_hash); + dump_cfg_fmtint(sExposeAuthInfo, o->expose_userauth_info); ++ dump_cfg_fmtint(sLogFormatJson, o->log_format_json); + dump_cfg_fmtint(sRefuseConnection, o->refuse_connection); + + /* string arguments */ +@@ -3357,6 +3393,7 @@ dump_config(ServerOptions *o) + #if defined(__OpenBSD__) || defined(HAVE_SYS_SET_PROCESS_RDOMAIN) + dump_cfg_string(sRDomain, o->routing_domain); + #endif ++ dump_cfg_string(sLogFormatPrefix, o->log_format_prefix); + dump_cfg_string(sSshdSessionPath, o->sshd_session_path); + dump_cfg_string(sPerSourcePenaltyExemptList, o->per_source_penalty_exempt); + +@@ -3381,6 +3418,7 @@ dump_config(ServerOptions *o) + o->num_auth_methods, o->auth_methods); + dump_cfg_strarray_oneline(sLogVerbose, + o->num_log_verbose, o->log_verbose); ++ dump_cfg_strarray(sLogFormatKeys, o->num_log_format_keys, o->log_format_keys); + dump_cfg_strarray_oneline(sChannelTimeout, + o->num_channel_timeouts, o->channel_timeouts); + +Index: openssh-9.9p1/auth2-pubkey.c +=================================================================== +--- openssh-9.9p1.orig/auth2-pubkey.c ++++ openssh-9.9p1/auth2-pubkey.c +@@ -65,6 +65,7 @@ + #include "monitor_wrap.h" + #include "authfile.h" + #include "match.h" ++#include "slog.h" + #include "ssherr.h" + #include "channels.h" /* XXX for session.h */ + #include "session.h" /* XXX for child_set_env(); refactor? */ +@@ -584,7 +585,7 @@ user_cert_trusted_ca(struct passwd *pw, + goto out; + } + } +- ++ slog_set_cert_serial((unsigned long long)key->cert->serial); + /* Success */ + verbose("Accepted certificate ID \"%s\" (serial %llu) signed by " + "%s CA %s via %s", key->cert->key_id, +@@ -595,6 +596,7 @@ user_cert_trusted_ca(struct passwd *pw, + *authoptsp = final_opts; + final_opts = NULL; + } ++ slog_set_cert_id(key->cert->key_id); + ret = 1; + out: + sshauthopt_free(principals_opts); +Index: openssh-9.9p1/regress/test-exec.sh +=================================================================== +--- openssh-9.9p1.orig/regress/test-exec.sh ++++ openssh-9.9p1/regress/test-exec.sh +@@ -894,7 +894,7 @@ start_sshd () + + trace "wait for sshd" + i=0; +- while [ ! -f $PIDFILE -a $i -lt 10 ]; do ++ while [ ! -f $PIDFILE -a $i -lt 3 ]; do + i=`expr $i + 1` + sleep $i + done +Index: openssh-9.9p1/session.c +=================================================================== +--- openssh-9.9p1.orig/session.c ++++ openssh-9.9p1/session.c +@@ -94,6 +94,8 @@ + #include "monitor_wrap.h" + #include "sftp.h" + #include "atomicio.h" ++#include "slog.h" ++ + + #if defined(KRB5) && defined(USE_AFS) + #include +@@ -762,6 +764,7 @@ do_exec(struct ssh *ssh, Session *s, con + ssh_remote_ipaddr(ssh), + ssh_remote_port(ssh), + s->self); ++ slog_log_session(); + + #ifdef SSH_AUDIT_EVENTS + if (s->command != NULL || s->command_handle != -1) +@@ -1472,7 +1475,7 @@ do_setusercontext(struct passwd *pw) + perror("unable to set user context (setuser)"); + exit(1); + } +- /* ++ /* + * FreeBSD's setusercontext() will not apply the user's + * own umask setting unless running with the user's UID. + */ +@@ -2152,7 +2155,7 @@ session_exec_req(struct ssh *ssh, Sessio + sshpkt_fatal(ssh, r, "%s: parse packet", __func__); + + channel_set_xtype(ssh, s->chanid, "session:command"); +- ++ slog_set_command(command); + success = do_exec(ssh, s, command) == 0; + free(command); + return success; +@@ -2872,4 +2875,3 @@ session_get_remote_name_or_ip(struct ssh + remote = ssh_remote_ipaddr(ssh); + return remote; + } +- +Index: openssh-9.9p1/log.h +=================================================================== +--- openssh-9.9p1.orig/log.h ++++ openssh-9.9p1/log.h +@@ -86,6 +86,8 @@ void sshfatal(const char *, const char + void sshlogdirect(LogLevel, int, const char *, ...) + __attribute__((format(printf, 3, 4))); + ++void do_log_slog_payload(const char *); ++ + #define do_log2(level, ...) sshlog(__FILE__, __func__, __LINE__, 0, level, NULL, __VA_ARGS__) + #define debug3(...) sshlog(__FILE__, __func__, __LINE__, 0, SYSLOG_LEVEL_DEBUG3, NULL, __VA_ARGS__) + #define debug2(...) sshlog(__FILE__, __func__, __LINE__, 0, SYSLOG_LEVEL_DEBUG2, NULL, __VA_ARGS__) +Index: openssh-9.9p1/log.c +=================================================================== +--- openssh-9.9p1.orig/log.c ++++ openssh-9.9p1/log.c +@@ -547,3 +547,39 @@ sshlogdirect(LogLevel level, int forced, + va_end(args); + } + ++ ++/* Custom function to log to syslog, to handle the session logging code ++*/ ++void ++do_log_slog_payload(const char *payload) ++{ ++#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT) ++ struct syslog_data sdata = SYSLOG_DATA_INIT; ++#endif ++ int pri = LOG_INFO; ++ int saved_errno = errno; ++ LogLevel level = SYSLOG_LEVEL_INFO; ++ log_handler_fn *tmp_handler; ++ ++ if (log_handler != NULL) { ++ /* Avoid recursion */ ++ tmp_handler = log_handler; ++ log_handler = NULL; ++ tmp_handler(level, 0, payload, log_handler_ctx); ++ log_handler = tmp_handler; ++ } else if (log_on_stderr) { ++ (void)write(log_stderr_fd, payload, strlen(payload)); ++ (void)write(log_stderr_fd, "\r\n", 2); ++ } else { ++#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT) ++ openlog_r(argv0 ? argv0 : __progname, LOG_PID, log_facility, &sdata); ++ syslog_r(pri, &sdata, "%s", payload); ++ closelog_r(&sdata); ++#else ++ openlog(argv0 ? argv0 : __progname, LOG_PID, log_facility); ++ syslog(pri, "%s", payload); ++ closelog(); ++#endif ++ } ++ errno = saved_errno; ++} +Index: openssh-9.9p1/slog.h +=================================================================== +--- /dev/null ++++ openssh-9.9p1/slog.h +@@ -0,0 +1,41 @@ ++/* ++ * Copyright 2004-present Facebook. All Rights Reserved. ++ */ ++#ifndef USE_SLOG ++#define USE_SLOG ++ ++#define SLOG_STRING_LEN 100 ++#define SLOG_MEDIUM_STRING_LEN 1000 ++#define SLOG_SHORT_STRING_LEN 50 ++#define SLOG_LONG_STRING_LEN 2000 ++ ++typedef enum { ++ SLOG_SESSION_INIT, ++ SLOG_SESSION_OPEN, ++ SLOG_SESSION_CLOSED, ++} SLOG_SESSION_STATE; ++ ++typedef enum { ++ SLOG_UNAUTHORIZED = 0, ++ SLOG_AUTHORIZED = 1 ++} SLOG_AUTHENTICATED; ++ ++void slog_init(void); ++ ++// setters ++void slog_pam_session_opened(void); ++void slog_set_auth_data(int , const char *, const char *); ++void slog_set_cert_id(const char *); ++void slog_set_cert_serial(unsigned long long ); ++void slog_set_connection(const char *, int, const char *, int, const char *); ++void slog_set_command(const char *); ++void slog_set_principal(const char *); ++void slog_set_user(const char *); ++void slog_set_auth_info(const char *); ++void slog_set_client_version(const char *); ++ ++// loggers ++void slog_exit_handler(void); ++void slog_log_session(void); ++ ++#endif +Index: openssh-9.9p1/sshd.c +=================================================================== +--- openssh-9.9p1.orig/sshd.c ++++ openssh-9.9p1/sshd.c +@@ -93,6 +93,8 @@ + #include "sk-api.h" + #include "addr.h" + #include "srclimit.h" ++#include "slog.h" ++#include + + /* Re-exec fds */ + #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) +@@ -275,6 +277,12 @@ child_register(int pipefd, int sockfd) + raddr = get_peer_ipaddr(sockfd); + xasprintf(&child->id, "connection from %s to %s", raddr, laddr); + } ++ slog_set_connection(raddr, ++ rport, ++ laddr, ++ lport, ++ get_log_session_id()); ++ + free(laddr); + free(raddr); + if (++children_active > options.max_startups) +@@ -1713,6 +1721,7 @@ main(int ac, char **av) + } + /* Reinitialize the log (because of the fork above). */ + log_init(__progname, options.log_level, options.log_facility, log_stderr); ++ slog_init(); + + if (FIPS_mode()) { + debug("FIPS mode initialized"); +@@ -1819,6 +1828,7 @@ main(int ac, char **av) + void + cleanup_exit(int i) + { ++ slog_exit_handler(); + _exit(i); + } + +Index: openssh-9.9p1/sshd_config +=================================================================== +--- openssh-9.9p1.orig/sshd_config ++++ openssh-9.9p1/sshd_config +@@ -33,6 +33,15 @@ Include /etc/ssh/sshd_config.d/*.conf + # Logging + #SyslogFacility AUTH + #LogLevel INFO ++# Structured logging ++# The default is no structured logging, to enable structured logging at least ++# one LogFormatKeys must be set. LogFormatJson defaults to no (array) to keep ++# the log line size smaller, if keys are desired set LogFormatJson to yes ++LogFormatPrefix sshd_auth_msg: ++LogFormatKeys server_ip server_port remote_ip remote_port pid session_id method ++LogFormatKeys cert_id cert_serial principal user session_state auth_successful ++LogFormatKeys start_time command ++LogFormatJson yes + + # Authentication: + +Index: openssh-9.9p1/auth-pam.c +=================================================================== +--- openssh-9.9p1.orig/auth-pam.c ++++ openssh-9.9p1/auth-pam.c +@@ -89,6 +89,7 @@ + #include "auth-pam.h" + #include "canohost.h" + #include "log.h" ++#include "slog.h" + #include "msg.h" + #include "packet.h" + #include "misc.h" +@@ -1222,9 +1223,12 @@ do_pam_session(struct ssh *ssh) + if (sshpam_err != PAM_SUCCESS) + fatal("PAM: failed to set PAM_CONV: %s", + pam_strerror(sshpam_handle, sshpam_err)); ++ + sshpam_err = pam_open_session(sshpam_handle, 0); +- if (sshpam_err == PAM_SUCCESS) ++ if (sshpam_err == PAM_SUCCESS) { ++ slog_pam_session_opened(); + sshpam_session_open = 1; ++ } + else { + sshpam_session_open = 0; + auth_restrict_session(ssh); +Index: openssh-9.9p1/servconf.h +=================================================================== +--- openssh-9.9p1.orig/servconf.h ++++ openssh-9.9p1/servconf.h +@@ -20,6 +20,8 @@ + + #define MAX_PORTS 256 /* Max # ports. */ + ++#define MAX_LOGFORMAT_KEYS 256 /* Max # LogFormatKeys */ ++ + /* permit_root_login */ + #define PERMIT_NOT_SET -1 + #define PERMIT_NO 0 +@@ -251,6 +253,11 @@ typedef struct { + u_int64_t timing_secret; + char *sk_provider; + int required_rsa_size; /* minimum size of RSA keys */ ++ ++ char *log_format_prefix; ++ u_int num_log_format_keys; ++ char *log_format_keys[MAX_LOGFORMAT_KEYS]; ++ int log_format_json; /* 1 to return "token": "token_val" in log format */ + + char **channel_timeouts; /* inactivity timeout by channel type */ + u_int num_channel_timeouts; +Index: openssh-9.9p1/regress/slog.sh +=================================================================== +--- /dev/null ++++ openssh-9.9p1/regress/slog.sh +@@ -0,0 +1,59 @@ ++tid='structured log' ++ ++port="4242" ++log_prefix="sshd_auth_msg:" ++log_keys="server_ip server_port remote_ip remote_port pid session_id method cert_id cert_serial principal user session_state auth_successful _time command end_time duration auth_info client_version" ++do_log_json="yes" ++test_config="$OBJ/sshd2_config" ++old_config="$OBJ/sshd_config" ++PIDFILE=$OBJ/pidfile ++ ++cat << EOF > $test_config ++ #*: ++ StrictModes no ++ Port $port ++ AddressFamily inet ++ ListenAddress 127.0.0.1 ++ #ListenAddress ::1 ++ PidFile $PIDFILE ++ AuthorizedKeysFile $OBJ/authorized_keys_%u ++ LogLevel ERROR ++ AcceptEnv _XXX_TEST_* ++ AcceptEnv _XXX_TEST ++ HostKey $OBJ/host.ssh-ed25519 ++ LogFormatPrefix $log_prefix ++ LogFormatJson $do_log_json ++ LogFormatKeys $log_keys ++EOF ++ ++ ++cp $test_config $old_config ++start_sshd ++ ++${SSH} -F $OBJ/ssh_config somehost true ++if [ $? -ne 0 ]; then ++ fail "ssh connect with failed" ++fi ++ ++test_log_counts() { ++ cnt=$(grep -c "$log_prefix" "$TEST_SSHD_LOGFILE") ++ if [ $cnt -ne 2 ]; then ++ fail "expected 2 structured logging lines, got $cnt" ++ fi ++} ++ ++test_json_valid() { ++ which python &>/dev/null || echo 'python not found in path, skipping tests' ++ ++ loglines=$(cat "$TEST_SSHD_LOGFILE" | grep "$log_prefix") ++ first=$(echo "$loglines" | head -n1) ++ last=$(echo "$loglines" | tail -n1) ++ ++ echo ${first:$(expr length $log_prefix)} | python -m json.tool &>/dev/null \ ++ || fail "invalid json structure $first" ++ echo ${last:$(expr length $log_prefix)} | python -m json.tool &>/dev/null \ ++ || fail "invalid json structure $last" ++} ++ ++test_log_counts ++test_json_valid +Index: openssh-9.9p1/Makefile.in +=================================================================== +--- openssh-9.9p1.orig/Makefile.in ++++ openssh-9.9p1/Makefile.in +@@ -125,7 +125,7 @@ SSHOBJS= ssh.o readconf.o clientloop.o s + + SSHDOBJS=sshd.o \ + platform-listen.o \ +- servconf.o sshpty.o srclimit.o groupaccess.o auth2-methods.o \ ++ servconf.o sshpty.o srclimit.o slog.o groupaccess.o auth2-methods.o \ + dns.o fatal.o compat.o utf8.o authfd.o canohost.o \ + $(SKOBJS) + +@@ -135,7 +135,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rh + auth.o auth2.o auth2-methods.o auth-options.o session.o \ + auth2-chall.o groupaccess.o \ + auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ +- auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \ ++ auth2-none.o auth2-passwd.o slog.o auth2-pubkey.o auth2-pubkeyfile.o \ + monitor.o monitor_wrap.o auth-krb5.o kexgsss.o \ + auth2-gss.o gss-serv.o gss-serv-krb5.o \ + loginrec.o auth-pam.o auth-shadow.o auth-sia.o \ +@@ -321,7 +321,7 @@ distclean: regressclean + rm -f *.o *.a $(TARGETS) logintest config.cache config.log + rm -f *.out core opensshd.init openssh.xml + rm -f Makefile buildpkg.sh config.h config.status +- rm -f survey.sh openbsd-compat/regress/Makefile *~ ++ rm -f survey.sh openbsd-compat/regress/Makefile *~ + rm -rf autom4te.cache + rm -f regress/check-perm + rm -f regress/mkdtemp +Index: openssh-9.9p1/auth.c +=================================================================== +--- openssh-9.9p1.orig/auth.c ++++ openssh-9.9p1/auth.c +@@ -75,6 +75,7 @@ + #include "monitor_wrap.h" + #include "ssherr.h" + #include "channels.h" ++#include "slog.h" + + /* import */ + extern ServerOptions options; +@@ -305,6 +306,7 @@ auth_log(struct ssh *ssh, int authentica + extra != NULL ? extra : ""); + + free(extra); ++ slog_set_auth_data(authenticated, method, authctxt->user); + + #if defined(CUSTOM_FAILED_LOGIN) || defined(SSH_AUDIT_EVENTS) + if (authenticated == 0 && !(authctxt->postponed || partial)) { +@@ -462,6 +464,7 @@ check_key_in_hostfiles(struct passwd *pw + struct passwd * + getpwnamallow(struct ssh *ssh, const char *user) + { ++ slog_set_user(user); + #ifdef HAVE_LOGIN_CAP + extern login_cap_t *lc; + #ifdef HAVE_AUTH_HOSTOK +Index: openssh-9.9p1/auth2-pubkeyfile.c +=================================================================== +--- openssh-9.9p1.orig/auth2-pubkeyfile.c ++++ openssh-9.9p1/auth2-pubkeyfile.c +@@ -50,6 +50,7 @@ + #include "authfile.h" + #include "match.h" + #include "ssherr.h" ++#include "slog.h" + + int + auth_authorise_keyopts(struct passwd *pw, struct sshauthopt *opts, +@@ -249,6 +250,7 @@ auth_process_principals(FILE *f, const c + snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum); + if (auth_check_principals_line(cp, cert, loc, authoptsp) == 0) + found_principal = 1; ++ slog_set_principal(cp); + } + debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum); + free(line); diff --git a/openssh-8.7p1-openssl-log.patch b/openssh-8.7p1-openssl-log.patch new file mode 100644 index 0000000000000000000000000000000000000000..67a56e33bf47e277c3b6e0fb59beb2dfa764ff45 --- /dev/null +++ b/openssh-8.7p1-openssl-log.patch @@ -0,0 +1,75 @@ +diff -up openssh-9.9p1/log.c.xxx openssh-9.9p1/log.c +--- openssh-9.9p1/log.c.xxx 2024-10-22 11:55:44.281939275 +0200 ++++ openssh-9.9p1/log.c 2024-10-22 11:56:16.709676267 +0200 +@@ -52,6 +52,9 @@ + + #include "log.h" + #include "match.h" ++#ifdef WITH_OPENSSL ++#include ++#endif + + static LogLevel log_level = SYSLOG_LEVEL_INFO; + static int log_on_stderr = 1; +@@ -438,6 +438,26 @@ sshlog(const char *file, const char *fun + va_end(args); + } + ++#ifdef WITH_OPENSSL ++static int ++openssl_error_print_cb(const char *str, size_t len, void *u) ++{ ++ sshlogdirect(SYSLOG_LEVEL_DEBUG1, 0, "openssl error %s", str); ++ return 0; ++} ++#endif ++ ++void ++sshlog_openssl(int r) ++{ ++#ifdef WITH_OPENSSL ++ if (r != SSH_ERR_LIBCRYPTO_ERROR) return; ++ ++ ERR_print_errors_cb(openssl_error_print_cb, NULL); ++#endif ++ return; ++} ++ + void + sshlogdie(const char *file, const char *func, int line, int showfunc, + LogLevel level, const char *suffix, const char *fmt, ...) +diff -up openssh-8.7p1/log.h.xxx openssh-8.7p1/log.h +--- openssh-8.7p1/log.h.xxx 2024-10-18 12:56:18.944971946 +0200 ++++ openssh-8.7p1/log.h 2024-10-18 13:03:38.324351416 +0200 +@@ -71,6 +71,7 @@ void cleanup_exit(int) __attribute__((n + void sshlog(const char *, const char *, int, int, + LogLevel, const char *, const char *, ...) + __attribute__((format(printf, 7, 8))); ++void sshlog_openssl(int); + void sshlogv(const char *, const char *, int, int, + LogLevel, const char *, const char *, va_list); + void sshlogdie(const char *, const char *, int, int, +diff -up openssh-8.7p1/auth2-pubkey.c.yyy openssh-8.7p1/auth2-pubkey.c +--- openssh-8.7p1/auth2-pubkey.c.yyy 2024-10-18 13:27:00.709055845 +0200 ++++ openssh-8.7p1/auth2-pubkey.c 2024-10-18 13:27:31.638784460 +0200 +@@ -131,6 +131,7 @@ userauth_pubkey(struct ssh *ssh) + goto done; + } + if ((r = sshkey_from_blob(pkblob, blen, &key)) != 0) { ++ sshlog_openssl(r); + error_fr(r, "parse key"); + goto done; + } +diff -up openssh-8.7p1/dispatch.c.yyy openssh-8.7p1/dispatch.c +--- openssh-8.7p1/dispatch.c.yyy 2024-10-18 13:27:56.349366570 +0200 ++++ openssh-8.7p1/dispatch.c 2024-10-18 13:28:17.921874757 +0200 +@@ -130,6 +130,8 @@ ssh_dispatch_run_fatal(struct ssh *ssh, + { + int r; + +- if ((r = ssh_dispatch_run(ssh, mode, done)) != 0) ++ if ((r = ssh_dispatch_run(ssh, mode, done)) != 0) { ++ sshlog_openssl(r); + sshpkt_fatal(ssh, r, "%s", __func__); ++ } + } diff --git a/openssh-8.7p1-redhat-help.patch b/openssh-8.7p1-redhat-help.patch new file mode 100644 index 0000000000000000000000000000000000000000..3ffb65778d77568df41e8dab540b9b743df4e81c --- /dev/null +++ b/openssh-8.7p1-redhat-help.patch @@ -0,0 +1,40 @@ +diff -up openssh-8.7p1/ssh.c.xxx openssh-8.7p1/ssh.c +--- openssh-8.7p1/ssh.c.xxx 2024-09-11 14:24:06.711088878 +0200 ++++ openssh-8.7p1/ssh.c 2024-09-11 14:35:12.883765718 +0200 +@@ -175,6 +175,16 @@ extern int muxserver_sock; + extern u_int muxclient_command; + + /* Prints a help message to the user. This function never returns. */ ++static void ++redhat_usage(void) ++{ ++ if(isatty(fileno(stderr))) { ++ fprintf(stderr, ++"\nYou can find some explanations for typical errors at this link:\n" ++" https://red.ht/support_rhel_ssh\n" ++ ); ++ } ++} + + static void + usage(void) +@@ -188,6 +196,7 @@ usage(void) + " destination [command [argument ...]]\n" + " ssh [-Q query_option]\n" + ); ++ redhat_usage(); + exit(255); + } + +@@ -1609,8 +1618,10 @@ main(int ac, char **av) + /* Open a connection to the remote host. */ + if (ssh_connect(ssh, host, options.host_arg, addrs, &hostaddr, + options.port, options.connection_attempts, +- &timeout_ms, options.tcp_keep_alive) != 0) ++ &timeout_ms, options.tcp_keep_alive) != 0) { ++ redhat_usage(); + exit(255); ++ } + + if (addrs != NULL) + freeaddrinfo(addrs); diff --git a/openssh-9.0p1-evp-fips-kex.patch b/openssh-9.0p1-evp-fips-kex.patch new file mode 100644 index 0000000000000000000000000000000000000000..36fd1cf2a792f1eb214cd1ed9fcaeaa3d2b711c8 --- /dev/null +++ b/openssh-9.0p1-evp-fips-kex.patch @@ -0,0 +1,595 @@ +diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/dh.c openssh-9.0p1-patched/dh.c +--- openssh-9.0p1/dh.c 2023-05-25 09:24:28.730868316 +0200 ++++ openssh-9.0p1-patched/dh.c 2023-05-25 09:23:44.841379532 +0200 +@@ -37,6 +37,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "dh.h" + #include "pathnames.h" +@@ -290,10 +293,15 @@ + int + dh_gen_key(DH *dh, int need) + { +- int pbits; +- const BIGNUM *dh_p, *pub_key; ++ const BIGNUM *dh_p, *dh_g; ++ BIGNUM *pub_key = NULL, *priv_key = NULL; ++ EVP_PKEY *pkey = NULL; ++ EVP_PKEY_CTX *ctx = NULL; ++ OSSL_PARAM_BLD *param_bld = NULL; ++ OSSL_PARAM *params = NULL; ++ int pbits, r = 0; + +- DH_get0_pqg(dh, &dh_p, NULL, NULL); ++ DH_get0_pqg(dh, &dh_p, NULL, &dh_g); + + if (need < 0 || dh_p == NULL || + (pbits = BN_num_bits(dh_p)) <= 0 || +@@ -301,19 +309,85 @@ + return SSH_ERR_INVALID_ARGUMENT; + if (need < 256) + need = 256; ++ ++ if ((param_bld = OSSL_PARAM_BLD_new()) == NULL || ++ (ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL)) == NULL) { ++ OSSL_PARAM_BLD_free(param_bld); ++ return SSH_ERR_ALLOC_FAIL; ++ } ++ ++ if (OSSL_PARAM_BLD_push_BN(param_bld, ++ OSSL_PKEY_PARAM_FFC_P, dh_p) != 1 || ++ OSSL_PARAM_BLD_push_BN(param_bld, ++ OSSL_PKEY_PARAM_FFC_G, dh_g) != 1) { ++ error_f("Could not set p,q,g parameters"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } + /* + * Pollard Rho, Big step/Little Step attacks are O(sqrt(n)), + * so double requested need here. + */ +- if (!DH_set_length(dh, MINIMUM(need * 2, pbits - 1))) +- return SSH_ERR_LIBCRYPTO_ERROR; +- +- if (DH_generate_key(dh) == 0) +- return SSH_ERR_LIBCRYPTO_ERROR; +- DH_get0_key(dh, &pub_key, NULL); +- if (!dh_pub_is_valid(dh, pub_key)) +- return SSH_ERR_INVALID_FORMAT; +- return 0; ++ if (OSSL_PARAM_BLD_push_int(param_bld, ++ OSSL_PKEY_PARAM_DH_PRIV_LEN, ++ MINIMUM(need * 2, pbits - 1)) != 1 || ++ (params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (EVP_PKEY_fromdata_init(ctx) != 1) { ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (EVP_PKEY_fromdata(ctx, &pkey, ++ EVP_PKEY_KEY_PARAMETERS, params) != 1) { ++ error_f("Failed key generation"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ ++ /* reuse context for key generation */ ++ EVP_PKEY_CTX_free(ctx); ++ ctx = NULL; ++ ++ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL || ++ EVP_PKEY_keygen_init(ctx) != 1) { ++ error_f("Could not create or init context"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (EVP_PKEY_generate(ctx, &pkey) != 1) { ++ error_f("Could not generate keys"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (EVP_PKEY_public_check(ctx) != 1) { ++ error_f("The public key is incorrect"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ ++ if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, ++ &pub_key) != 1 || ++ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, ++ &priv_key) != 1 || ++ DH_set0_key(dh, pub_key, priv_key) != 1) { ++ error_f("Could not set pub/priv keys to DH struct"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ ++ /* transferred */ ++ pub_key = NULL; ++ priv_key = NULL; ++out: ++ OSSL_PARAM_free(params); ++ OSSL_PARAM_BLD_free(param_bld); ++ EVP_PKEY_CTX_free(ctx); ++ EVP_PKEY_free(pkey); ++ BN_clear_free(pub_key); ++ BN_clear_free(priv_key); ++ return r; + } + + DH * +diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/kex.c openssh-9.0p1-patched/kex.c +--- openssh-9.0p1/kex.c 2023-05-25 09:24:28.731868327 +0200 ++++ openssh-9.0p1-patched/kex.c 2023-05-25 09:23:44.841379532 +0200 +@@ -1623,3 +1623,142 @@ + return r; + } + ++#ifdef WITH_OPENSSL ++/* ++ * Creates an EVP_PKEY from the given parameters and keys. ++ * The private key can be omitted. ++ */ ++EVP_PKEY * ++sshkey_create_evp(OSSL_PARAM_BLD *param_bld, EVP_PKEY_CTX *ctx) ++{ ++ EVP_PKEY *ret = NULL; ++ OSSL_PARAM *params = NULL; ++ if (param_bld == NULL || ctx == NULL) { ++ debug2_f("param_bld or ctx is NULL"); ++ return NULL; ++ } ++ if ((params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { ++ debug2_f("Could not build param list"); ++ return NULL; ++ } ++ if (EVP_PKEY_fromdata_init(ctx) != 1 || ++ EVP_PKEY_fromdata(ctx, &ret, EVP_PKEY_KEYPAIR, params) != 1) { ++ debug2_f("EVP_PKEY_fromdata failed"); ++ OSSL_PARAM_free(params); ++ return NULL; ++ } ++ return ret; ++} ++ ++int ++kex_create_evp_ec(EC_KEY *k, int ecdsa_nid, EVP_PKEY **pkey) ++{ ++ OSSL_PARAM_BLD *param_bld = NULL; ++ EVP_PKEY_CTX *ctx = NULL; ++ BN_CTX *bn_ctx = NULL; ++ uint8_t *pub_ser = NULL; ++ const char *group_name; ++ const EC_POINT *pub = NULL; ++ const BIGNUM *priv = NULL; ++ int ret = 0; ++ ++ if (k == NULL) ++ return SSH_ERR_INVALID_ARGUMENT; ++ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL || ++ (param_bld = OSSL_PARAM_BLD_new()) == NULL || ++ (bn_ctx = BN_CTX_new()) == NULL) { ++ ret = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ if ((group_name = OSSL_EC_curve_nid2name(ecdsa_nid)) == NULL || ++ OSSL_PARAM_BLD_push_utf8_string(param_bld, ++ OSSL_PKEY_PARAM_GROUP_NAME, ++ group_name, ++ strlen(group_name)) != 1) { ++ ret = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if ((pub = EC_KEY_get0_public_key(k)) != NULL) { ++ const EC_GROUP *group; ++ size_t len; ++ ++ group = EC_KEY_get0_group(k); ++ len = EC_POINT_point2oct(group, pub, ++ POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); ++ if ((pub_ser = malloc(len)) == NULL) { ++ ret = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ EC_POINT_point2oct(group, ++ pub, ++ POINT_CONVERSION_UNCOMPRESSED, ++ pub_ser, ++ len, ++ bn_ctx); ++ if (OSSL_PARAM_BLD_push_octet_string(param_bld, ++ OSSL_PKEY_PARAM_PUB_KEY, ++ pub_ser, ++ len) != 1) { ++ ret = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ } ++ if ((priv = EC_KEY_get0_private_key(k)) != NULL && ++ OSSL_PARAM_BLD_push_BN(param_bld, ++ OSSL_PKEY_PARAM_PRIV_KEY, priv) != 1) { ++ ret = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if ((*pkey = sshkey_create_evp(param_bld, ctx)) == NULL) { ++ ret = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ ++out: ++ OSSL_PARAM_BLD_free(param_bld); ++ EVP_PKEY_CTX_free(ctx); ++ BN_CTX_free(bn_ctx); ++ free(pub_ser); ++ return ret; ++} ++ ++int ++kex_create_evp_dh(EVP_PKEY **pkey, const BIGNUM *p, const BIGNUM *q, ++ const BIGNUM *g, const BIGNUM *pub, const BIGNUM *priv) ++{ ++ OSSL_PARAM_BLD *param_bld = NULL; ++ EVP_PKEY_CTX *ctx = NULL; ++ int r = 0; ++ ++ /* create EVP_PKEY-DH key */ ++ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL)) == NULL || ++ (param_bld = OSSL_PARAM_BLD_new()) == NULL) { ++ error_f("EVP_PKEY_CTX or PARAM_BLD init failed"); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ if (OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, p) != 1 || ++ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_Q, q) != 1 || ++ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, g) != 1 || ++ OSSL_PARAM_BLD_push_BN(param_bld, ++ OSSL_PKEY_PARAM_PUB_KEY, pub) != 1) { ++ error_f("Failed pushing params to OSSL_PARAM_BLD"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (priv != NULL && ++ OSSL_PARAM_BLD_push_BN(param_bld, ++ OSSL_PKEY_PARAM_PRIV_KEY, priv) != 1) { ++ error_f("Failed pushing private key to OSSL_PARAM_BLD"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if ((*pkey = sshkey_create_evp(param_bld, ctx)) == NULL) ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++out: ++ OSSL_PARAM_BLD_free(param_bld); ++ EVP_PKEY_CTX_free(ctx); ++ return r; ++} ++#endif /* WITH_OPENSSL */ +diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/kexdh.c openssh-9.0p1-patched/kexdh.c +--- openssh-9.0p1/kexdh.c 2023-05-25 09:24:28.674867692 +0200 ++++ openssh-9.0p1-patched/kexdh.c 2023-05-25 09:25:28.494533889 +0200 +@@ -35,6 +35,10 @@ + + #include "openbsd-compat/openssl-compat.h" + #include ++#include ++#include ++#include ++#include + + #include "sshkey.h" + #include "kex.h" +@@ -83,9 +87,12 @@ + kex_dh_compute_key(struct kex *kex, BIGNUM *dh_pub, struct sshbuf *out) + { + BIGNUM *shared_secret = NULL; ++ const BIGNUM *pub, *priv, *p, *q, *g; ++ EVP_PKEY *pkey = NULL, *dh_pkey = NULL; ++ EVP_PKEY_CTX *ctx = NULL; + u_char *kbuf = NULL; + size_t klen = 0; +- int kout, r; ++ int r = 0; + + #ifdef DEBUG_KEXDH + fprintf(stderr, "dh_pub= "); +@@ -100,24 +107,59 @@ + r = SSH_ERR_MESSAGE_INCOMPLETE; + goto out; + } +- klen = DH_size(kex->dh); ++ ++ DH_get0_key(kex->dh, &pub, &priv); ++ DH_get0_pqg(kex->dh, &p, &q, &g); ++ /* import key */ ++ r = kex_create_evp_dh(&pkey, p, q, g, pub, priv); ++ if (r != 0) { ++ error_f("Could not create EVP_PKEY for dh"); ++ ERR_print_errors_fp(stderr); ++ goto out; ++ } ++ /* import peer key ++ * the parameters should be the same as with pkey ++ */ ++ r = kex_create_evp_dh(&dh_pkey, p, q, g, dh_pub, NULL); ++ if (r != 0) { ++ error_f("Could not import peer key for dh"); ++ ERR_print_errors_fp(stderr); ++ goto out; ++ } ++ ++ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL) { ++ error_f("Could not init EVP_PKEY_CTX for dh"); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ if (EVP_PKEY_derive_init(ctx) != 1 || ++ EVP_PKEY_derive_set_peer(ctx, dh_pkey) != 1 || ++ EVP_PKEY_derive(ctx, NULL, &klen) != 1) { ++ error_f("Could not get key size"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } + if ((kbuf = malloc(klen)) == NULL || + (shared_secret = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } +- if ((kout = DH_compute_key(kbuf, dh_pub, kex->dh)) < 0 || +- BN_bin2bn(kbuf, kout, shared_secret) == NULL) { ++ if (EVP_PKEY_derive(ctx, kbuf, &klen) != 1 || ++ BN_bin2bn(kbuf, klen, shared_secret) == NULL) { ++ error_f("Could not derive key"); + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } + #ifdef DEBUG_KEXDH +- dump_digest("shared secret", kbuf, kout); ++ dump_digest("shared secret", kbuf, klen); + #endif + r = sshbuf_put_bignum2(out, shared_secret); + out: + freezero(kbuf, klen); + BN_clear_free(shared_secret); ++ EVP_PKEY_free(pkey); ++ EVP_PKEY_free(dh_pkey); ++ EVP_PKEY_CTX_free(ctx); + return r; + } + +diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/kex.h openssh-9.0p1-patched/kex.h +--- openssh-9.0p1/kex.h 2023-05-25 09:24:28.725868260 +0200 ++++ openssh-9.0p1-patched/kex.h 2023-05-25 09:23:44.841379532 +0200 +@@ -33,6 +33,9 @@ + # include + # include + # include ++# include ++# include ++# include + # ifdef OPENSSL_HAS_ECC + # include + # else /* OPENSSL_HAS_ECC */ +@@ -283,6 +286,9@@ + const u_char pub[CURVE25519_SIZE], struct sshbuf *out, int) + __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) + __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); ++int kex_create_evp_dh(EVP_PKEY **, const BIGNUM *, const BIGNUM *, ++ const BIGNUM *, const BIGNUM *, const BIGNUM *); ++int kex_create_evp_ec(EC_KEY *k, int ecdsa_nid, EVP_PKEY **pkey); + + #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) + void dump_digest(const char *, const u_char *, int); +diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac ../openssh-8.7p1/kexecdh.c ./kexecdh.c +--- ../openssh-8.7p1/kexecdh.c 2021-08-20 06:03:49.000000000 +0200 ++++ ./kexecdh.c 2023-04-13 14:30:14.882449593 +0200 +@@ -35,17 +35,57 @@ + #include + + #include ++#include ++#include ++#include ++#include + + #include "sshkey.h" + #include "kex.h" + #include "sshbuf.h" + #include "digest.h" + #include "ssherr.h" ++#include "log.h" + + static int + kex_ecdh_dec_key_group(struct kex *, const struct sshbuf *, EC_KEY *key, + const EC_GROUP *, struct sshbuf **); + ++static EC_KEY * ++generate_ec_keys(int ec_nid) ++{ ++ EC_KEY *client_key = NULL; ++ EVP_PKEY *pkey = NULL; ++ EVP_PKEY_CTX *ctx = NULL; ++ OSSL_PARAM_BLD *param_bld = NULL; ++ OSSL_PARAM *params = NULL; ++ const char *group_name; ++ ++ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL || ++ (param_bld = OSSL_PARAM_BLD_new()) == NULL) ++ goto out; ++ if ((group_name = OSSL_EC_curve_nid2name(ec_nid)) == NULL || ++ OSSL_PARAM_BLD_push_utf8_string(param_bld, ++ OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) != 1 || ++ (params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { ++ error_f("Could not create OSSL_PARAM"); ++ goto out; ++ } ++ if (EVP_PKEY_keygen_init(ctx) != 1 || ++ EVP_PKEY_CTX_set_params(ctx, params) != 1 || ++ EVP_PKEY_generate(ctx, &pkey) != 1 || ++ (client_key = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) { ++ error_f("Could not generate ec keys"); ++ goto out; ++ } ++out: ++ EVP_PKEY_free(pkey); ++ EVP_PKEY_CTX_free(ctx); ++ OSSL_PARAM_BLD_free(param_bld); ++ OSSL_PARAM_free(params); ++ return client_key; ++} ++ + int + kex_ecdh_keypair(struct kex *kex) + { +@@ -55,11 +95,7 @@ + struct sshbuf *buf = NULL; + int r; + +- if ((client_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) { +- r = SSH_ERR_ALLOC_FAIL; +- goto out; +- } +- if (EC_KEY_generate_key(client_key) != 1) { ++ if ((client_key = generate_ec_keys(kex->ec_nid)) == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } +@@ -101,11 +137,7 @@ + *server_blobp = NULL; + *shared_secretp = NULL; + +- if ((server_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) { +- r = SSH_ERR_ALLOC_FAIL; +- goto out; +- } +- if (EC_KEY_generate_key(server_key) != 1) { ++ if ((server_key = generate_ec_keys(kex->ec_nid)) == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } +@@ -140,11 +172,21 @@ + { + struct sshbuf *buf = NULL; + BIGNUM *shared_secret = NULL; +- EC_POINT *dh_pub = NULL; +- u_char *kbuf = NULL; +- size_t klen = 0; ++ EVP_PKEY_CTX *ctx = NULL; ++ EVP_PKEY *pkey = NULL, *dh_pkey = NULL; ++ OSSL_PARAM_BLD *param_bld = NULL; ++ OSSL_PARAM *params = NULL; ++ u_char *kbuf = NULL, *pub = NULL; ++ size_t klen = 0, publen; ++ const char *group_name; + int r; + ++ /* import EC_KEY to EVP_PKEY */ ++ if ((r = kex_create_evp_ec(key, kex->ec_nid, &pkey)) != 0) { ++ error_f("Could not create EVP_PKEY"); ++ goto out; ++ } ++ + *shared_secretp = NULL; + + if ((buf = sshbuf_new()) == NULL) { +@@ -153,45 +195,82 @@ + } + if ((r = sshbuf_put_stringb(buf, ec_blob)) != 0) + goto out; +- if ((dh_pub = EC_POINT_new(group)) == NULL) { ++ ++ /* the public key is in the buffer in octet string UNCOMPRESSED ++ * format. See sshbuf_put_ec */ ++ if ((r = sshbuf_get_string(buf, &pub, &publen)) != 0) ++ goto out; ++ sshbuf_reset(buf); ++ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL || ++ (param_bld = OSSL_PARAM_BLD_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } +- if ((r = sshbuf_get_ec(buf, dh_pub, group)) != 0) { ++ if ((group_name = OSSL_EC_curve_nid2name(kex->ec_nid)) == NULL) { ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (OSSL_PARAM_BLD_push_octet_string(param_bld, ++ OSSL_PKEY_PARAM_PUB_KEY, pub, publen) != 1 || ++ OSSL_PARAM_BLD_push_utf8_string(param_bld, ++ OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) != 1 || ++ (params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { ++ error_f("Failed to set params for dh_pkey"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (EVP_PKEY_fromdata_init(ctx) != 1 || ++ EVP_PKEY_fromdata(ctx, &dh_pkey, ++ EVP_PKEY_PUBLIC_KEY, params) != 1 || ++ EVP_PKEY_public_check(ctx) != 1) { ++ error_f("Peer public key import failed"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } +- sshbuf_reset(buf); + + #ifdef DEBUG_KEXECDH + fputs("public key:\n", stderr); +- sshkey_dump_ec_point(group, dh_pub); ++ EVP_PKEY_print_public_fp(stderr, dh_pkey, 0, NULL); + #endif +- if (sshkey_ec_validate_public(group, dh_pub) != 0) { +- r = SSH_ERR_MESSAGE_INCOMPLETE; ++ EVP_PKEY_CTX_free(ctx); ++ ctx = NULL; ++ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL || ++ EVP_PKEY_derive_init(ctx) != 1 || ++ EVP_PKEY_derive_set_peer(ctx, dh_pkey) != 1 || ++ EVP_PKEY_derive(ctx, NULL, &klen) != 1) { ++ error_f("Failed to get derive information"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } +- klen = (EC_GROUP_get_degree(group) + 7) / 8; +- if ((kbuf = malloc(klen)) == NULL || +- (shared_secret = BN_new()) == NULL) { ++ if ((kbuf = malloc(klen)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } +- if (ECDH_compute_key(kbuf, klen, dh_pub, key, NULL) != (int)klen || +- BN_bin2bn(kbuf, klen, shared_secret) == NULL) { ++ if (EVP_PKEY_derive(ctx, kbuf, &klen) != 1) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } + #ifdef DEBUG_KEXECDH + dump_digest("shared secret", kbuf, klen); + #endif ++ if ((shared_secret = BN_new()) == NULL || ++ (BN_bin2bn(kbuf, klen, shared_secret) == NULL)) { ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } + if ((r = sshbuf_put_bignum2(buf, shared_secret)) != 0) + goto out; + *shared_secretp = buf; + buf = NULL; + out: +- EC_POINT_clear_free(dh_pub); ++ EVP_PKEY_CTX_free(ctx); ++ EVP_PKEY_free(pkey); ++ EVP_PKEY_free(dh_pkey); ++ OSSL_PARAM_BLD_free(param_bld); ++ OSSL_PARAM_free(params); + BN_clear_free(shared_secret); + freezero(kbuf, klen); ++ freezero(pub, publen); + sshbuf_free(buf); + return r; + } diff --git a/openssh-9.6p1-gssapi-keyex.patch b/openssh-9.6p1-gssapi-keyex.patch new file mode 100644 index 0000000000000000000000000000000000000000..ef1f97e660360b5c9cf7289efc33442a56ae28a9 --- /dev/null +++ b/openssh-9.6p1-gssapi-keyex.patch @@ -0,0 +1,4200 @@ +diff --color -ruNp a/auth2.c b/auth2.c +--- a/auth2.c 2024-09-16 11:45:56.858133241 +0200 ++++ b/auth2.c 2024-09-16 11:46:34.688939755 +0200 +@@ -71,6 +71,7 @@ extern Authmethod method_passwd; + extern Authmethod method_kbdint; + extern Authmethod method_hostbased; + #ifdef GSSAPI ++extern Authmethod method_gsskeyex; + extern Authmethod method_gssapi; + #endif + +@@ -78,6 +79,7 @@ Authmethod *authmethods[] = { + &method_none, + &method_pubkey, + #ifdef GSSAPI ++ &method_gsskeyex, + &method_gssapi, + #endif + &method_passwd, +diff --color -ruNp a/auth2-gss.c b/auth2-gss.c +--- a/auth2-gss.c 2024-09-16 11:45:56.858133241 +0200 ++++ b/auth2-gss.c 2024-09-16 11:46:34.689939776 +0200 +@@ -51,6 +51,7 @@ + #define SSH_GSSAPI_MAX_MECHS 2048 + + extern ServerOptions options; ++extern struct authmethod_cfg methodcfg_gsskeyex; + extern struct authmethod_cfg methodcfg_gssapi; + + static int input_gssapi_token(int type, u_int32_t plen, struct ssh *ssh); +@@ -59,6 +60,48 @@ static int input_gssapi_exchange_complet + static int input_gssapi_errtok(int, u_int32_t, struct ssh *); + + /* ++ * The 'gssapi_keyex' userauth mechanism. ++ */ ++static int ++userauth_gsskeyex(struct ssh *ssh, const char *method) ++{ ++ Authctxt *authctxt = ssh->authctxt; ++ int r, authenticated = 0; ++ struct sshbuf *b = NULL; ++ gss_buffer_desc mic, gssbuf; ++ u_char *p; ++ size_t len; ++ ++ if ((r = sshpkt_get_string(ssh, &p, &len)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal_fr(r, "parsing"); ++ ++ if ((b = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new failed"); ++ ++ mic.value = p; ++ mic.length = len; ++ ++ ssh_gssapi_buildmic(b, authctxt->user, authctxt->service, ++ "gssapi-keyex", ssh->kex->session_id); ++ ++ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL) ++ fatal_f("sshbuf_mutable_ptr failed"); ++ gssbuf.length = sshbuf_len(b); ++ ++ /* gss_kex_context is NULL with privsep, so we can't check it here */ ++ if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gss_kex_context, ++ &gssbuf, &mic))) ++ authenticated = mm_ssh_gssapi_userok(authctxt->user, ++ authctxt->pw, 1); ++ ++ sshbuf_free(b); ++ free(mic.value); ++ ++ return (authenticated); ++} ++ ++/* + * We only support those mechanisms that we know about (ie ones that we know + * how to check local user kuserok and the like) + */ +@@ -267,7 +310,7 @@ input_gssapi_exchange_complete(int type, + if ((r = sshpkt_get_end(ssh)) != 0) + fatal_fr(r, "parse packet"); + +- authenticated = mm_ssh_gssapi_userok(authctxt->user); ++ authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 1); + + authctxt->postponed = 0; + ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); +@@ -315,7 +358,7 @@ input_gssapi_mic(int type, u_int32_t ple + gssbuf.length = sshbuf_len(b); + + if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic))) +- authenticated = mm_ssh_gssapi_userok(authctxt->user); ++ authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 0); + else + logit("GSSAPI MIC check failed"); + +@@ -333,6 +376,11 @@ input_gssapi_mic(int type, u_int32_t ple + return 0; + } + ++Authmethod method_gsskeyex = { ++ &methodcfg_gsskeyex, ++ userauth_gsskeyex, ++}; ++ + Authmethod method_gssapi = { + &methodcfg_gssapi, + userauth_gssapi, +diff --color -ruNp a/auth2-methods.c b/auth2-methods.c +--- a/auth2-methods.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/auth2-methods.c 2024-09-16 11:46:34.689939776 +0200 +@@ -50,6 +50,11 @@ struct authmethod_cfg methodcfg_pubkey = + &options.pubkey_authentication + }; + #ifdef GSSAPI ++struct authmethod_cfg methodcfg_gsskeyex = { ++ "gssapi-keyex", ++ NULL, ++ &options.gss_authentication ++}; + struct authmethod_cfg methodcfg_gssapi = { + "gssapi-with-mic", + NULL, +@@ -76,6 +81,7 @@ static struct authmethod_cfg *authmethod + &methodcfg_none, + &methodcfg_pubkey, + #ifdef GSSAPI ++ &methodcfg_gsskeyex, + &methodcfg_gssapi, + #endif + &methodcfg_passwd, +diff --color -ruNp a/auth.c b/auth.c +--- a/auth.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/auth.c 2024-09-16 11:46:34.690939798 +0200 +@@ -356,7 +356,8 @@ auth_root_allowed(struct ssh *ssh, const + case PERMIT_NO_PASSWD: + if (strcmp(method, "publickey") == 0 || + strcmp(method, "hostbased") == 0 || +- strcmp(method, "gssapi-with-mic") == 0) ++ strcmp(method, "gssapi-with-mic") == 0 || ++ strcmp(method, "gssapi-keyex") == 0) + return 1; + break; + case PERMIT_FORCED_ONLY: +diff --color -ruNp a/canohost.c b/canohost.c +--- a/canohost.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/canohost.c 2024-09-16 11:46:34.690939798 +0200 +@@ -35,6 +35,99 @@ + #include "canohost.h" + #include "misc.h" + ++/* ++ * Returns the remote DNS hostname as a string. The returned string must not ++ * be freed. NB. this will usually trigger a DNS query the first time it is ++ * called. ++ * This function does additional checks on the hostname to mitigate some ++ * attacks on legacy rhosts-style authentication. ++ * XXX is RhostsRSAAuthentication vulnerable to these? ++ * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?) ++ */ ++ ++char * ++remote_hostname(struct ssh *ssh) ++{ ++ struct sockaddr_storage from; ++ socklen_t fromlen; ++ struct addrinfo hints, *ai, *aitop; ++ char name[NI_MAXHOST], ntop2[NI_MAXHOST]; ++ const char *ntop = ssh_remote_ipaddr(ssh); ++ ++ /* Get IP address of client. */ ++ fromlen = sizeof(from); ++ memset(&from, 0, sizeof(from)); ++ if (getpeername(ssh_packet_get_connection_in(ssh), ++ (struct sockaddr *)&from, &fromlen) == -1) { ++ debug("getpeername failed: %.100s", strerror(errno)); ++ return xstrdup(ntop); ++ } ++ ++ ipv64_normalise_mapped(&from, &fromlen); ++ if (from.ss_family == AF_INET6) ++ fromlen = sizeof(struct sockaddr_in6); ++ ++ debug3("Trying to reverse map address %.100s.", ntop); ++ /* Map the IP address to a host name. */ ++ if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name), ++ NULL, 0, NI_NAMEREQD) != 0) { ++ /* Host name not found. Use ip address. */ ++ return xstrdup(ntop); ++ } ++ ++ /* ++ * if reverse lookup result looks like a numeric hostname, ++ * someone is trying to trick us by PTR record like following: ++ * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5 ++ */ ++ memset(&hints, 0, sizeof(hints)); ++ hints.ai_socktype = SOCK_DGRAM; /*dummy*/ ++ hints.ai_flags = AI_NUMERICHOST; ++ if (getaddrinfo(name, NULL, &hints, &ai) == 0) { ++ logit("Nasty PTR record \"%s\" is set up for %s, ignoring", ++ name, ntop); ++ freeaddrinfo(ai); ++ return xstrdup(ntop); ++ } ++ ++ /* Names are stored in lowercase. */ ++ lowercase(name); ++ ++ /* ++ * Map it back to an IP address and check that the given ++ * address actually is an address of this host. This is ++ * necessary because anyone with access to a name server can ++ * define arbitrary names for an IP address. Mapping from ++ * name to IP address can be trusted better (but can still be ++ * fooled if the intruder has access to the name server of ++ * the domain). ++ */ ++ memset(&hints, 0, sizeof(hints)); ++ hints.ai_family = from.ss_family; ++ hints.ai_socktype = SOCK_STREAM; ++ if (getaddrinfo(name, NULL, &hints, &aitop) != 0) { ++ logit("reverse mapping checking getaddrinfo for %.700s " ++ "[%s] failed.", name, ntop); ++ return xstrdup(ntop); ++ } ++ /* Look for the address from the list of addresses. */ ++ for (ai = aitop; ai; ai = ai->ai_next) { ++ if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2, ++ sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 && ++ (strcmp(ntop, ntop2) == 0)) ++ break; ++ } ++ freeaddrinfo(aitop); ++ /* If we reached the end of the list, the address was not there. */ ++ if (ai == NULL) { ++ /* Address not found for the host name. */ ++ logit("Address %.100s maps to %.600s, but this does not " ++ "map back to the address.", ntop, name); ++ return xstrdup(ntop); ++ } ++ return xstrdup(name); ++} ++ + void + ipv64_normalise_mapped(struct sockaddr_storage *addr, socklen_t *len) + { +diff --color -ruNp a/canohost.h b/canohost.h +--- a/canohost.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/canohost.h 2024-09-16 11:46:34.690939798 +0200 +@@ -15,6 +15,9 @@ + #ifndef _CANOHOST_H + #define _CANOHOST_H + ++struct ssh; ++ ++char *remote_hostname(struct ssh *); + char *get_peer_ipaddr(int); + int get_peer_port(int); + char *get_local_ipaddr(int); +diff --color -ruNp a/clientloop.c b/clientloop.c +--- a/clientloop.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/clientloop.c 2024-09-16 11:46:34.690939798 +0200 +@@ -115,6 +115,10 @@ + #include "ssherr.h" + #include "hostfile.h" + ++#ifdef GSSAPI ++#include "ssh-gss.h" ++#endif ++ + /* Permitted RSA signature algorithms for UpdateHostkeys proofs */ + #define HOSTKEY_PROOF_RSA_ALGS "rsa-sha2-512,rsa-sha2-256" + +@@ -1590,6 +1594,14 @@ client_loop(struct ssh *ssh, int have_pt + /* Do channel operations. */ + channel_after_poll(ssh, pfd, npfd_active); + ++#ifdef GSSAPI ++ if (options.gss_renewal_rekey && ++ ssh_gssapi_credentials_updated(NULL)) { ++ debug("credentials updated - forcing rekey"); ++ need_rekeying = 1; ++ } ++#endif ++ + /* Buffer input from the connection. */ + if (conn_in_ready) + client_process_net_input(ssh); +diff --color -ruNp a/configure.ac b/configure.ac +--- a/configure.ac 2024-09-16 11:45:56.870133497 +0200 ++++ b/configure.ac 2024-09-16 11:46:34.691939819 +0200 +@@ -774,6 +774,30 @@ int main(void) { if (NSVersionOfRunTimeL + [Use tunnel device compatibility to OpenBSD]) + AC_DEFINE([SSH_TUN_PREPEND_AF], [1], + [Prepend the address family to IP tunnel traffic]) ++ AC_MSG_CHECKING([if we have the Security Authorization Session API]) ++ AC_TRY_COMPILE([#include ], ++ [SessionCreate(0, 0);], ++ [ac_cv_use_security_session_api="yes" ++ AC_DEFINE([USE_SECURITY_SESSION_API], [1], ++ [platform has the Security Authorization Session API]) ++ LIBS="$LIBS -framework Security" ++ AC_MSG_RESULT([yes])], ++ [ac_cv_use_security_session_api="no" ++ AC_MSG_RESULT([no])]) ++ AC_MSG_CHECKING([if we have an in-memory credentials cache]) ++ AC_TRY_COMPILE( ++ [#include ], ++ [cc_context_t c; ++ (void) cc_initialize (&c, 0, NULL, NULL);], ++ [AC_DEFINE([USE_CCAPI], [1], ++ [platform uses an in-memory credentials cache]) ++ LIBS="$LIBS -framework Security" ++ AC_MSG_RESULT([yes]) ++ if test "x$ac_cv_use_security_session_api" = "xno"; then ++ AC_MSG_ERROR([*** Need a security framework to use the credentials cache API ***]) ++ fi], ++ [AC_MSG_RESULT([no])] ++ ) + m4_pattern_allow([AU_IPv]) + AC_CHECK_DECL([AU_IPv4], [], + AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records]) +diff --color -ruNp a/gss-genr.c b/gss-genr.c +--- a/gss-genr.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/gss-genr.c 2024-09-16 11:46:34.708940181 +0200 +@@ -42,9 +42,33 @@ + #include "sshbuf.h" + #include "log.h" + #include "ssh2.h" ++#include "cipher.h" ++#include "sshkey.h" ++#include "kex.h" ++#include "digest.h" ++#include "packet.h" + + #include "ssh-gss.h" + ++typedef struct { ++ char *encoded; ++ gss_OID oid; ++} ssh_gss_kex_mapping; ++ ++/* ++ * XXX - It would be nice to find a more elegant way of handling the ++ * XXX passing of the key exchange context to the userauth routines ++ */ ++ ++Gssctxt *gss_kex_context = NULL; ++ ++static ssh_gss_kex_mapping *gss_enc2oid = NULL; ++ ++int ++ssh_gssapi_oid_table_ok(void) { ++ return (gss_enc2oid != NULL); ++} ++ + /* sshbuf_get for gss_buffer_desc */ + int + ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g) +@@ -60,6 +84,159 @@ ssh_gssapi_get_buffer_desc(struct sshbuf + return 0; + } + ++/* sshpkt_get of gss_buffer_desc */ ++int ++ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *ssh, gss_buffer_desc *g) ++{ ++ int r; ++ u_char *p; ++ size_t len; ++ ++ if ((r = sshpkt_get_string(ssh, &p, &len)) != 0) ++ return r; ++ g->value = p; ++ g->length = len; ++ return 0; ++} ++ ++/* ++ * Return a list of the gss-group1-sha1 mechanisms supported by this program ++ * ++ * We test mechanisms to ensure that we can use them, to avoid starting ++ * a key exchange with a bad mechanism ++ */ ++ ++char * ++ssh_gssapi_client_mechanisms(const char *host, const char *client, ++ const char *kex) { ++ gss_OID_set gss_supported = NULL; ++ OM_uint32 min_status; ++ ++ if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported))) ++ return NULL; ++ ++ return ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism, ++ host, client, kex); ++} ++ ++char * ++ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check, ++ const char *host, const char *client, const char *kex) { ++ struct sshbuf *buf = NULL; ++ size_t i; ++ int r = SSH_ERR_ALLOC_FAIL; ++ int oidpos, enclen; ++ char *mechs, *encoded; ++ u_char digest[SSH_DIGEST_MAX_LENGTH]; ++ char deroid[2]; ++ struct ssh_digest_ctx *md = NULL; ++ char *s, *cp, *p; ++ ++ if (gss_enc2oid != NULL) { ++ for (i = 0; gss_enc2oid[i].encoded != NULL; i++) ++ free(gss_enc2oid[i].encoded); ++ free(gss_enc2oid); ++ } ++ ++ gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) * ++ (gss_supported->count + 1)); ++ ++ if ((buf = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new failed"); ++ ++ oidpos = 0; ++ s = cp = xstrdup(kex); ++ for (i = 0; i < gss_supported->count; i++) { ++ if (gss_supported->elements[i].length < 128 && ++ (*check)(NULL, &(gss_supported->elements[i]), host, client)) { ++ ++ deroid[0] = SSH_GSS_OIDTYPE; ++ deroid[1] = gss_supported->elements[i].length; ++ ++ if ((md = ssh_digest_start(SSH_DIGEST_MD5)) == NULL || ++ (r = ssh_digest_update(md, deroid, 2)) != 0 || ++ (r = ssh_digest_update(md, ++ gss_supported->elements[i].elements, ++ gss_supported->elements[i].length)) != 0 || ++ (r = ssh_digest_final(md, digest, sizeof(digest))) != 0) ++ fatal_fr(r, "digest failed"); ++ ssh_digest_free(md); ++ md = NULL; ++ ++ encoded = xmalloc(ssh_digest_bytes(SSH_DIGEST_MD5) ++ * 2); ++ enclen = __b64_ntop(digest, ++ ssh_digest_bytes(SSH_DIGEST_MD5), encoded, ++ ssh_digest_bytes(SSH_DIGEST_MD5) * 2); ++ ++ cp = strncpy(s, kex, strlen(kex)); ++ for ((p = strsep(&cp, ",")); p && *p != '\0'; ++ (p = strsep(&cp, ","))) { ++ if (sshbuf_len(buf) != 0 && ++ (r = sshbuf_put_u8(buf, ',')) != 0) ++ fatal_fr(r, "sshbuf_put_u8 error"); ++ if ((r = sshbuf_put(buf, p, strlen(p))) != 0 || ++ (r = sshbuf_put(buf, encoded, enclen)) != 0) ++ fatal_fr(r, "sshbuf_put error"); ++ } ++ ++ gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]); ++ gss_enc2oid[oidpos].encoded = encoded; ++ oidpos++; ++ } ++ } ++ free(s); ++ gss_enc2oid[oidpos].oid = NULL; ++ gss_enc2oid[oidpos].encoded = NULL; ++ ++ if ((mechs = sshbuf_dup_string(buf)) == NULL) ++ fatal_f("sshbuf_dup_string failed"); ++ ++ sshbuf_free(buf); ++ ++ if (strlen(mechs) == 0) { ++ free(mechs); ++ mechs = NULL; ++ } ++ ++ return (mechs); ++} ++ ++gss_OID ++ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) { ++ int i = 0; ++ ++#define SKIP_KEX_NAME(type) \ ++ case type: \ ++ if (strlen(name) < sizeof(type##_ID)) \ ++ return GSS_C_NO_OID; \ ++ name += sizeof(type##_ID) - 1; \ ++ break; ++ ++ switch (kex_type) { ++ SKIP_KEX_NAME(KEX_GSS_GRP1_SHA1) ++ SKIP_KEX_NAME(KEX_GSS_GRP14_SHA1) ++ SKIP_KEX_NAME(KEX_GSS_GRP14_SHA256) ++ SKIP_KEX_NAME(KEX_GSS_GRP16_SHA512) ++ SKIP_KEX_NAME(KEX_GSS_GEX_SHA1) ++ SKIP_KEX_NAME(KEX_GSS_NISTP256_SHA256) ++ SKIP_KEX_NAME(KEX_GSS_C25519_SHA256) ++ default: ++ return GSS_C_NO_OID; ++ } ++ ++#undef SKIP_KEX_NAME ++ ++ while (gss_enc2oid[i].encoded != NULL && ++ strcmp(name, gss_enc2oid[i].encoded) != 0) ++ i++; ++ ++ if (gss_enc2oid[i].oid != NULL && ctx != NULL) ++ ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid); ++ ++ return gss_enc2oid[i].oid; ++} ++ + /* Check that the OID in a data stream matches that in the context */ + int + ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) +@@ -168,6 +345,7 @@ ssh_gssapi_build_ctx(Gssctxt **ctx) + (*ctx)->creds = GSS_C_NO_CREDENTIAL; + (*ctx)->client = GSS_C_NO_NAME; + (*ctx)->client_creds = GSS_C_NO_CREDENTIAL; ++ (*ctx)->first = 1; + } + + /* Delete our context, providing it has been built correctly */ +@@ -193,6 +371,12 @@ ssh_gssapi_delete_ctx(Gssctxt **ctx) + gss_release_name(&ms, &(*ctx)->client); + if ((*ctx)->client_creds != GSS_C_NO_CREDENTIAL) + gss_release_cred(&ms, &(*ctx)->client_creds); ++ sshbuf_free((*ctx)->shared_secret); ++ sshbuf_free((*ctx)->server_pubkey); ++ sshbuf_free((*ctx)->server_host_key_blob); ++ sshbuf_free((*ctx)->server_blob); ++ explicit_bzero((*ctx)->hash, sizeof((*ctx)->hash)); ++ BN_clear_free((*ctx)->dh_client_pub); + + free(*ctx); + *ctx = NULL; +@@ -216,7 +400,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int de + } + + ctx->major = gss_init_sec_context(&ctx->minor, +- GSS_C_NO_CREDENTIAL, &ctx->context, ctx->name, ctx->oid, ++ ctx->client_creds, &ctx->context, ctx->name, ctx->oid, + GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, + 0, NULL, recv_tok, NULL, send_tok, flags, NULL); + +@@ -246,8 +430,42 @@ ssh_gssapi_import_name(Gssctxt *ctx, con + } + + OM_uint32 ++ssh_gssapi_client_identity(Gssctxt *ctx, const char *name) ++{ ++ gss_buffer_desc gssbuf; ++ gss_name_t gssname; ++ OM_uint32 status; ++ gss_OID_set oidset; ++ ++ gssbuf.value = (void *) name; ++ gssbuf.length = strlen(gssbuf.value); ++ ++ gss_create_empty_oid_set(&status, &oidset); ++ gss_add_oid_set_member(&status, ctx->oid, &oidset); ++ ++ ctx->major = gss_import_name(&ctx->minor, &gssbuf, ++ GSS_C_NT_USER_NAME, &gssname); ++ ++ if (!ctx->major) ++ ctx->major = gss_acquire_cred(&ctx->minor, ++ gssname, 0, oidset, GSS_C_INITIATE, ++ &ctx->client_creds, NULL, NULL); ++ ++ gss_release_name(&status, &gssname); ++ gss_release_oid_set(&status, &oidset); ++ ++ if (ctx->major) ++ ssh_gssapi_error(ctx); ++ ++ return(ctx->major); ++} ++ ++OM_uint32 + ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) + { ++ if (ctx == NULL) ++ return -1; ++ + if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, + GSS_C_QOP_DEFAULT, buffer, hash))) + ssh_gssapi_error(ctx); +@@ -255,6 +473,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer + return (ctx->major); + } + ++/* Priviledged when used by server */ ++OM_uint32 ++ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) ++{ ++ if (ctx == NULL) ++ return -1; ++ ++ ctx->major = gss_verify_mic(&ctx->minor, ctx->context, ++ gssbuf, gssmic, NULL); ++ ++ return (ctx->major); ++} ++ + void + ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service, + const char *context, const struct sshbuf *session_id) +@@ -271,11 +502,16 @@ ssh_gssapi_buildmic(struct sshbuf *b, co + } + + int +-ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) ++ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host, ++ const char *client) + { + gss_buffer_desc token = GSS_C_EMPTY_BUFFER; + OM_uint32 major, minor; + gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; ++ Gssctxt *intctx = NULL; ++ ++ if (ctx == NULL) ++ ctx = &intctx; + + /* RFC 4462 says we MUST NOT do SPNEGO */ + if (oid->length == spnego_oid.length && +@@ -285,6 +521,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx + ssh_gssapi_build_ctx(ctx); + ssh_gssapi_set_oid(*ctx, oid); + major = ssh_gssapi_import_name(*ctx, host); ++ ++ if (!GSS_ERROR(major) && client) ++ major = ssh_gssapi_client_identity(*ctx, client); ++ + if (!GSS_ERROR(major)) { + major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, + NULL); +@@ -294,10 +534,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx + GSS_C_NO_BUFFER); + } + +- if (GSS_ERROR(major)) ++ if (GSS_ERROR(major) || intctx != NULL) + ssh_gssapi_delete_ctx(ctx); + + return (!GSS_ERROR(major)); + } + ++int ++ssh_gssapi_credentials_updated(Gssctxt *ctxt) { ++ static gss_name_t saved_name = GSS_C_NO_NAME; ++ static OM_uint32 saved_lifetime = 0; ++ static gss_OID saved_mech = GSS_C_NO_OID; ++ static gss_name_t name; ++ static OM_uint32 last_call = 0; ++ OM_uint32 lifetime, now, major, minor; ++ int equal; ++ ++ now = time(NULL); ++ ++ if (ctxt) { ++ debug("Rekey has happened - updating saved versions"); ++ ++ if (saved_name != GSS_C_NO_NAME) ++ gss_release_name(&minor, &saved_name); ++ ++ major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, ++ &saved_name, &saved_lifetime, NULL, NULL); ++ ++ if (!GSS_ERROR(major)) { ++ saved_mech = ctxt->oid; ++ saved_lifetime+= now; ++ } else { ++ /* Handle the error */ ++ } ++ return 0; ++ } ++ ++ if (now - last_call < 10) ++ return 0; ++ ++ last_call = now; ++ ++ if (saved_mech == GSS_C_NO_OID) ++ return 0; ++ ++ major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, ++ &name, &lifetime, NULL, NULL); ++ if (major == GSS_S_CREDENTIALS_EXPIRED) ++ return 0; ++ else if (GSS_ERROR(major)) ++ return 0; ++ ++ major = gss_compare_name(&minor, saved_name, name, &equal); ++ gss_release_name(&minor, &name); ++ if (GSS_ERROR(major)) ++ return 0; ++ ++ if (equal && (saved_lifetime < lifetime + now - 10)) ++ return 1; ++ ++ return 0; ++} ++ + #endif /* GSSAPI */ +diff --color -ruNp a/gss-serv.c b/gss-serv.c +--- a/gss-serv.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/gss-serv.c 2024-09-16 11:46:34.692939840 +0200 +@@ -1,7 +1,7 @@ + /* $OpenBSD: gss-serv.c,v 1.32 2020/03/13 03:17:07 djm Exp $ */ + + /* +- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. ++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions +@@ -44,17 +44,19 @@ + #include "session.h" + #include "misc.h" + #include "servconf.h" ++#include "uidswap.h" + + #include "ssh-gss.h" ++#include "monitor_wrap.h" + + extern ServerOptions options; + + static ssh_gssapi_client gssapi_client = +- { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, +- GSS_C_NO_CREDENTIAL, NULL, {NULL, NULL, NULL, NULL}}; ++ { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL, ++ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0}; + + ssh_gssapi_mech gssapi_null_mech = +- { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL}; ++ { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL}; + + #ifdef KRB5 + extern ssh_gssapi_mech gssapi_kerberos_mech; +@@ -141,6 +143,29 @@ ssh_gssapi_server_ctx(Gssctxt **ctx, gss + } + + /* Unprivileged */ ++char * ++ssh_gssapi_server_mechanisms(void) { ++ if (supported_oids == NULL) ++ ssh_gssapi_prepare_supported_oids(); ++ return (ssh_gssapi_kex_mechs(supported_oids, ++ &ssh_gssapi_server_check_mech, NULL, NULL, ++ options.gss_kex_algorithms)); ++} ++ ++/* Unprivileged */ ++int ++ssh_gssapi_server_check_mech(Gssctxt **dum, gss_OID oid, const char *data, ++ const char *dummy) { ++ Gssctxt *ctx = NULL; ++ int res; ++ ++ res = !GSS_ERROR(mm_ssh_gssapi_server_ctx(&ctx, oid)); ++ ssh_gssapi_delete_ctx(&ctx); ++ ++ return (res); ++} ++ ++/* Unprivileged */ + void + ssh_gssapi_supported_oids(gss_OID_set *oidset) + { +@@ -150,7 +175,9 @@ ssh_gssapi_supported_oids(gss_OID_set *o + gss_OID_set supported; + + gss_create_empty_oid_set(&min_status, oidset); +- gss_indicate_mechs(&min_status, &supported); ++ ++ if (GSS_ERROR(gss_indicate_mechs(&min_status, &supported))) ++ return; + + while (supported_mechs[i]->name != NULL) { + if (GSS_ERROR(gss_test_oid_set_member(&min_status, +@@ -276,8 +303,48 @@ OM_uint32 + ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) + { + int i = 0; ++ int equal = 0; ++ gss_name_t new_name = GSS_C_NO_NAME; ++ gss_buffer_desc ename = GSS_C_EMPTY_BUFFER; ++ ++ if (options.gss_store_rekey && client->used && ctx->client_creds) { ++ if (client->mech->oid.length != ctx->oid->length || ++ (memcmp(client->mech->oid.elements, ++ ctx->oid->elements, ctx->oid->length) !=0)) { ++ debug("Rekeyed credentials have different mechanism"); ++ return GSS_S_COMPLETE; ++ } + +- gss_buffer_desc ename; ++ if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor, ++ ctx->client_creds, ctx->oid, &new_name, ++ NULL, NULL, NULL))) { ++ ssh_gssapi_error(ctx); ++ return (ctx->major); ++ } ++ ++ ctx->major = gss_compare_name(&ctx->minor, client->name, ++ new_name, &equal); ++ ++ if (GSS_ERROR(ctx->major)) { ++ ssh_gssapi_error(ctx); ++ return (ctx->major); ++ } ++ ++ if (!equal) { ++ debug("Rekeyed credentials have different name"); ++ return GSS_S_COMPLETE; ++ } ++ ++ debug("Marking rekeyed credentials for export"); ++ ++ gss_release_name(&ctx->minor, &client->name); ++ gss_release_cred(&ctx->minor, &client->creds); ++ client->name = new_name; ++ client->creds = ctx->client_creds; ++ ctx->client_creds = GSS_C_NO_CREDENTIAL; ++ client->updated = 1; ++ return GSS_S_COMPLETE; ++ } + + client->mech = NULL; + +@@ -292,6 +359,13 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g + if (client->mech == NULL) + return GSS_S_FAILURE; + ++ if (ctx->client_creds && ++ (ctx->major = gss_inquire_cred_by_mech(&ctx->minor, ++ ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) { ++ ssh_gssapi_error(ctx); ++ return (ctx->major); ++ } ++ + if ((ctx->major = gss_display_name(&ctx->minor, ctx->client, + &client->displayname, NULL))) { + ssh_gssapi_error(ctx); +@@ -309,6 +383,8 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g + return (ctx->major); + } + ++ gss_release_buffer(&ctx->minor, &ename); ++ + /* We can't copy this structure, so we just move the pointer to it */ + client->creds = ctx->client_creds; + ctx->client_creds = GSS_C_NO_CREDENTIAL; +@@ -319,11 +395,20 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g + void + ssh_gssapi_cleanup_creds(void) + { +- if (gssapi_client.store.filename != NULL) { +- /* Unlink probably isn't sufficient */ +- debug("removing gssapi cred file\"%s\"", +- gssapi_client.store.filename); +- unlink(gssapi_client.store.filename); ++ krb5_ccache ccache = NULL; ++ krb5_error_code problem; ++ ++ if (gssapi_client.store.data != NULL) { ++ if ((problem = krb5_cc_resolve(gssapi_client.store.data, gssapi_client.store.envval, &ccache))) { ++ debug_f("krb5_cc_resolve(): %.100s", ++ krb5_get_err_text(gssapi_client.store.data, problem)); ++ } else if ((problem = krb5_cc_destroy(gssapi_client.store.data, ccache))) { ++ debug_f("krb5_cc_destroy(): %.100s", ++ krb5_get_err_text(gssapi_client.store.data, problem)); ++ } else { ++ krb5_free_context(gssapi_client.store.data); ++ gssapi_client.store.data = NULL; ++ } + } + } + +@@ -356,19 +441,23 @@ ssh_gssapi_do_child(char ***envp, u_int + + /* Privileged */ + int +-ssh_gssapi_userok(char *user) ++ssh_gssapi_userok(char *user, struct passwd *pw, int kex) + { + OM_uint32 lmin; + ++ (void) kex; /* used in privilege separation */ ++ + if (gssapi_client.exportedname.length == 0 || + gssapi_client.exportedname.value == NULL) { + debug("No suitable client data"); + return 0; + } + if (gssapi_client.mech && gssapi_client.mech->userok) +- if ((*gssapi_client.mech->userok)(&gssapi_client, user)) ++ if ((*gssapi_client.mech->userok)(&gssapi_client, user)) { ++ gssapi_client.used = 1; ++ gssapi_client.store.owner = pw; + return 1; +- else { ++ } else { + /* Destroy delegated credentials if userok fails */ + gss_release_buffer(&lmin, &gssapi_client.displayname); + gss_release_buffer(&lmin, &gssapi_client.exportedname); +@@ -382,14 +471,85 @@ ssh_gssapi_userok(char *user) + return (0); + } + +-/* Privileged */ +-OM_uint32 +-ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) ++/* These bits are only used for rekeying. The unpriviledged child is running ++ * as the user, the monitor is root. ++ * ++ * In the child, we want to : ++ * *) Ask the monitor to store our credentials into the store we specify ++ * *) If it succeeds, maybe do a PAM update ++ */ ++ ++/* Stuff for PAM */ ++ ++#ifdef USE_PAM ++static int ssh_gssapi_simple_conv(int n, const struct pam_message **msg, ++ struct pam_response **resp, void *data) + { +- ctx->major = gss_verify_mic(&ctx->minor, ctx->context, +- gssbuf, gssmic, NULL); ++ return (PAM_CONV_ERR); ++} ++#endif + +- return (ctx->major); ++void ++ssh_gssapi_rekey_creds(void) { ++ int ok; ++#ifdef USE_PAM ++ int ret; ++ pam_handle_t *pamh = NULL; ++ struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL}; ++ char *envstr; ++#endif ++ ++ if (gssapi_client.store.filename == NULL && ++ gssapi_client.store.envval == NULL && ++ gssapi_client.store.envvar == NULL) ++ return; ++ ++ ok = mm_ssh_gssapi_update_creds(&gssapi_client.store); ++ ++ if (!ok) ++ return; ++ ++ debug("Rekeyed credentials stored successfully"); ++ ++ /* Actually managing to play with the ssh pam stack from here will ++ * be next to impossible. In any case, we may want different options ++ * for rekeying. So, use our own :) ++ */ ++#ifdef USE_PAM ++ ret = pam_start("sshd-rekey", gssapi_client.store.owner->pw_name, ++ &pamconv, &pamh); ++ if (ret) ++ return; ++ ++ xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar, ++ gssapi_client.store.envval); ++ ++ ret = pam_putenv(pamh, envstr); ++ if (!ret) ++ pam_setcred(pamh, PAM_REINITIALIZE_CRED); ++ pam_end(pamh, PAM_SUCCESS); ++#endif ++} ++ ++int ++ssh_gssapi_update_creds(ssh_gssapi_ccache *store) { ++ int ok = 0; ++ ++ /* Check we've got credentials to store */ ++ if (!gssapi_client.updated) ++ return 0; ++ ++ gssapi_client.updated = 0; ++ ++ temporarily_use_uid(gssapi_client.store.owner); ++ if (gssapi_client.mech && gssapi_client.mech->updatecreds) ++ ok = (*gssapi_client.mech->updatecreds)(store, &gssapi_client); ++ else ++ debug("No update function for this mechanism"); ++ ++ restore_uid(); ++ ++ return ok; + } + + /* Privileged */ +diff --color -ruNp a/gss-serv-krb5.c b/gss-serv-krb5.c +--- a/gss-serv-krb5.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/gss-serv-krb5.c 2024-09-16 11:46:34.692939840 +0200 +@@ -1,7 +1,7 @@ + /* $OpenBSD: gss-serv-krb5.c,v 1.9 2018/07/09 21:37:55 markus Exp $ */ + + /* +- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. ++ * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions +@@ -120,7 +120,7 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl + krb5_error_code problem; + krb5_principal princ; + OM_uint32 maj_status, min_status; +- int len; ++ const char *new_ccname, *new_cctype; + const char *errmsg; + + if (client->creds == NULL) { +@@ -180,11 +180,26 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl + return; + } + +- client->store.filename = xstrdup(krb5_cc_get_name(krb_context, ccache)); ++ new_cctype = krb5_cc_get_type(krb_context, ccache); ++ new_ccname = krb5_cc_get_name(krb_context, ccache); ++ + client->store.envvar = "KRB5CCNAME"; +- len = strlen(client->store.filename) + 6; +- client->store.envval = xmalloc(len); +- snprintf(client->store.envval, len, "FILE:%s", client->store.filename); ++#ifdef USE_CCAPI ++ xasprintf(&client->store.envval, "API:%s", new_ccname); ++ client->store.filename = NULL; ++#else ++ if (new_ccname[0] == ':') ++ new_ccname++; ++ xasprintf(&client->store.envval, "%s:%s", new_cctype, new_ccname); ++ if (strcmp(new_cctype, "DIR") == 0) { ++ char *p; ++ p = strrchr(client->store.envval, '/'); ++ if (p) ++ *p = '\0'; ++ } ++ if ((strcmp(new_cctype, "FILE") == 0) || (strcmp(new_cctype, "DIR") == 0)) ++ client->store.filename = xstrdup(new_ccname); ++#endif + + #ifdef USE_PAM + if (options.use_pam) +@@ -193,9 +208,76 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl + + krb5_cc_close(krb_context, ccache); + ++ client->store.data = krb_context; ++ + return; + } + ++int ++ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store, ++ ssh_gssapi_client *client) ++{ ++ krb5_ccache ccache = NULL; ++ krb5_principal principal = NULL; ++ char *name = NULL; ++ krb5_error_code problem; ++ OM_uint32 maj_status, min_status; ++ ++ if ((problem = krb5_cc_resolve(krb_context, store->envval, &ccache))) { ++ logit("krb5_cc_resolve(): %.100s", ++ krb5_get_err_text(krb_context, problem)); ++ return 0; ++ } ++ ++ /* Find out who the principal in this cache is */ ++ if ((problem = krb5_cc_get_principal(krb_context, ccache, ++ &principal))) { ++ logit("krb5_cc_get_principal(): %.100s", ++ krb5_get_err_text(krb_context, problem)); ++ krb5_cc_close(krb_context, ccache); ++ return 0; ++ } ++ ++ if ((problem = krb5_unparse_name(krb_context, principal, &name))) { ++ logit("krb5_unparse_name(): %.100s", ++ krb5_get_err_text(krb_context, problem)); ++ krb5_free_principal(krb_context, principal); ++ krb5_cc_close(krb_context, ccache); ++ return 0; ++ } ++ ++ ++ if (strcmp(name,client->exportedname.value)!=0) { ++ debug("Name in local credentials cache differs. Not storing"); ++ krb5_free_principal(krb_context, principal); ++ krb5_cc_close(krb_context, ccache); ++ krb5_free_unparsed_name(krb_context, name); ++ return 0; ++ } ++ krb5_free_unparsed_name(krb_context, name); ++ ++ /* Name matches, so lets get on with it! */ ++ ++ if ((problem = krb5_cc_initialize(krb_context, ccache, principal))) { ++ logit("krb5_cc_initialize(): %.100s", ++ krb5_get_err_text(krb_context, problem)); ++ krb5_free_principal(krb_context, principal); ++ krb5_cc_close(krb_context, ccache); ++ return 0; ++ } ++ ++ krb5_free_principal(krb_context, principal); ++ ++ if ((maj_status = gss_krb5_copy_ccache(&min_status, client->creds, ++ ccache))) { ++ logit("gss_krb5_copy_ccache() failed. Sorry!"); ++ krb5_cc_close(krb_context, ccache); ++ return 0; ++ } ++ ++ return 1; ++} ++ + ssh_gssapi_mech gssapi_kerberos_mech = { + "toWM5Slw5Ew8Mqkay+al2g==", + "Kerberos", +@@ -203,7 +285,8 @@ ssh_gssapi_mech gssapi_kerberos_mech = { + NULL, + &ssh_gssapi_krb5_userok, + NULL, +- &ssh_gssapi_krb5_storecreds ++ &ssh_gssapi_krb5_storecreds, ++ &ssh_gssapi_krb5_updatecreds + }; + + #endif /* KRB5 */ +diff --color -ruNp a/kex.c b/kex.c +--- a/kex.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/kex.c 2024-09-16 11:46:34.692939840 +0200 +@@ -297,17 +297,37 @@ static int + kex_compose_ext_info_server(struct ssh *ssh, struct sshbuf *m) + { + int r; ++ int have_key = 0; ++ int ext_count = 2; ++ ++#ifdef GSSAPI ++ /* ++ * Currently GSS KEX don't provide host keys as optional message, so ++ * no reasons to announce the publickey-hostbound extension ++ */ ++ if (ssh->kex->gss == NULL) ++ have_key = 1; ++#endif ++ ext_count += have_key; ++ + + if (ssh->kex->server_sig_algs == NULL && + (ssh->kex->server_sig_algs = sshkey_alg_list(0, 1, 1, ',')) == NULL) + return SSH_ERR_ALLOC_FAIL; +- if ((r = sshbuf_put_u32(m, 3)) != 0 || ++ if ((r = sshbuf_put_u32(m, ext_count)) != 0 || + (r = sshbuf_put_cstring(m, "server-sig-algs")) != 0 || +- (r = sshbuf_put_cstring(m, ssh->kex->server_sig_algs)) != 0 || +- (r = sshbuf_put_cstring(m, +- "publickey-hostbound@openssh.com")) != 0 || +- (r = sshbuf_put_cstring(m, "0")) != 0 || +- (r = sshbuf_put_cstring(m, "ping@openssh.com")) != 0 || ++ (r = sshbuf_put_cstring(m, ssh->kex->server_sig_algs)) != 0) { ++ error_fr(r, "compose"); ++ return r; ++ } ++ if (have_key) { ++ if ((r = sshbuf_put_cstring(m, "publickey-hostbound@openssh.com")) != 0 || ++ (r = sshbuf_put_cstring(m, "0")) != 0) { ++ error_fr(r, "compose"); ++ return r; ++ } ++ } ++ if ((r = sshbuf_put_cstring(m, "ping@openssh.com")) != 0 || + (r = sshbuf_put_cstring(m, "0")) != 0) { + error_fr(r, "compose"); + return r; +@@ -737,6 +757,9 @@ kex_free(struct kex *kex) + sshbuf_free(kex->server_version); + sshbuf_free(kex->client_pub); + sshbuf_free(kex->session_id); ++#ifdef GSSAPI ++ free(kex->gss_host); ++#endif /* GSSAPI */ + sshbuf_free(kex->initial_sig); + sshkey_free(kex->initial_hostkey); + free(kex->failed_choice); +diff --color -ruNp a/kexdh.c b/kexdh.c +--- a/kexdh.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/kexdh.c 2024-09-16 11:46:34.693939862 +0200 +@@ -49,13 +49,23 @@ kex_dh_keygen(struct kex *kex) + { + switch (kex->kex_type) { + case KEX_DH_GRP1_SHA1: ++#ifdef GSSAPI ++ case KEX_GSS_GRP1_SHA1: ++#endif + kex->dh = dh_new_group1(); + break; + case KEX_DH_GRP14_SHA1: + case KEX_DH_GRP14_SHA256: ++#ifdef GSSAPI ++ case KEX_GSS_GRP14_SHA1: ++ case KEX_GSS_GRP14_SHA256: ++#endif + kex->dh = dh_new_group14(); + break; + case KEX_DH_GRP16_SHA512: ++#ifdef GSSAPI ++ case KEX_GSS_GRP16_SHA512: ++#endif + kex->dh = dh_new_group16(); + break; + case KEX_DH_GRP18_SHA512: +diff --color -ruNp a/kexgen.c b/kexgen.c +--- a/kexgen.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/kexgen.c 2024-09-16 11:46:34.693939862 +0200 +@@ -44,7 +44,7 @@ + static int input_kex_gen_init(int, u_int32_t, struct ssh *); + static int input_kex_gen_reply(int type, u_int32_t seq, struct ssh *ssh); + +-static int ++int + kex_gen_hash( + int hash_alg, + const struct sshbuf *client_version, +diff --color -ruNp a/kexgssc.c b/kexgssc.c +--- a/kexgssc.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/kexgssc.c 2024-10-14 15:18:02.491798105 +0200 +@@ -0,0 +1,706 @@ ++/* ++ * Copyright (c) 2001-2009 Simon Wilkinson. 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. ++ * ++ * 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" ++ ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ ++#include "includes.h" ++ ++#include ++#include ++ ++#include ++ ++#include "xmalloc.h" ++#include "sshbuf.h" ++#include "ssh2.h" ++#include "sshkey.h" ++#include "cipher.h" ++#include "kex.h" ++#include "log.h" ++#include "packet.h" ++#include "dh.h" ++#include "digest.h" ++#include "ssherr.h" ++ ++#include "ssh-gss.h" ++ ++static int input_kexgss_hostkey(int, u_int32_t, struct ssh *); ++static int input_kexgss_continue(int, u_int32_t, struct ssh *); ++static int input_kexgss_complete(int, u_int32_t, struct ssh *); ++static int input_kexgss_error(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_group(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_continue(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_complete(int, u_int32_t, struct ssh *); ++ ++static int ++kexgss_final(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ struct sshbuf *empty = NULL; ++ struct sshbuf *shared_secret = NULL; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ int r; ++ ++ /* ++ * We _must_ have received a COMPLETE message in reply from the ++ * server, which will have set server_blob and msg_tok ++ */ ++ ++ /* compute shared secret */ ++ switch (kex->kex_type) { ++ case KEX_GSS_GRP1_SHA1: ++ case KEX_GSS_GRP14_SHA1: ++ case KEX_GSS_GRP14_SHA256: ++ case KEX_GSS_GRP16_SHA512: ++ r = kex_dh_dec(kex, gss->server_blob, &shared_secret); ++ break; ++ case KEX_GSS_C25519_SHA256: ++ if (sshbuf_ptr(gss->server_blob)[sshbuf_len(gss->server_blob)] & 0x80) ++ fatal("The received key has MSB of last octet set!"); ++ r = kex_c25519_dec(kex, gss->server_blob, &shared_secret); ++ break; ++ case KEX_GSS_NISTP256_SHA256: ++ if (sshbuf_len(gss->server_blob) != 65) ++ fatal("The received NIST-P256 key did not match " ++ "expected length (expected 65, got %zu)", ++ sshbuf_len(gss->server_blob)); ++ ++ if (sshbuf_ptr(gss->server_blob)[0] != POINT_CONVERSION_UNCOMPRESSED) ++ fatal("The received NIST-P256 key does not have first octet 0x04"); ++ ++ r = kex_ecdh_dec(kex, gss->server_blob, &shared_secret); ++ break; ++ default: ++ r = SSH_ERR_INVALID_ARGUMENT; ++ break; ++ } ++ if (r != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ goto out; ++ } ++ ++ if ((empty = sshbuf_new()) == NULL) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ hashlen = sizeof(hash); ++ r = kex_gen_hash(kex->hash_alg, kex->client_version, ++ kex->server_version, kex->my, kex->peer, ++ (gss->server_host_key_blob ? gss->server_host_key_blob : empty), ++ kex->client_pub, gss->server_blob, shared_secret, ++ hash, &hashlen); ++ sshbuf_free(empty); ++ if (r != 0) ++ fatal_f("Unexpected KEX type %d", kex->kex_type); ++ ++ gss->buf.value = hash; ++ gss->buf.length = hashlen; ++ ++ /* Verify that the hash matches the MIC we just got. */ ++ if (GSS_ERROR(ssh_gssapi_checkmic(gss, &gss->buf, &gss->msg_tok))) ++ sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); ++ ++ gss_release_buffer(&gss->minor, &gss->msg_tok); ++ ++ if (kex->gss_deleg_creds) ++ ssh_gssapi_credentials_updated(gss); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = gss; ++ else ++ ssh_gssapi_delete_ctx(&kex->gss); ++ ++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) ++ r = kex_send_newkeys(ssh); ++ ++ if (kex->gss != NULL) { ++ sshbuf_free(gss->server_host_key_blob); ++ gss->server_host_key_blob = NULL; ++ sshbuf_free(gss->server_blob); ++ gss->server_blob = NULL; ++ } ++out: ++ explicit_bzero(kex->c25519_client_key, sizeof(kex->c25519_client_key)); ++ explicit_bzero(hash, sizeof(hash)); ++ sshbuf_free(shared_secret); ++ sshbuf_free(kex->client_pub); ++ kex->client_pub = NULL; ++ return r; ++} ++ ++static int ++kexgss_init_ctx(struct ssh *ssh, ++ gss_buffer_desc *token_ptr) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags; ++ int r; ++ ++ debug("Calling gss_init_sec_context"); ++ ++ gss->major = ssh_gssapi_init_ctx(gss, kex->gss_deleg_creds, ++ token_ptr, &send_tok, &ret_flags); ++ ++ if (GSS_ERROR(gss->major)) { ++ /* XXX Useless code: Missing send? */ ++ if (send_tok.length != 0) { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ fatal("gss_init_context failed"); ++ } ++ ++ /* If we've got an old receive buffer get rid of it */ ++ if (token_ptr != GSS_C_NO_BUFFER) ++ gss_release_buffer(&gss->minor, token_ptr); ++ ++ if (gss->major == GSS_S_COMPLETE) { ++ /* If mutual state flag is not true, kex fails */ ++ if (!(ret_flags & GSS_C_MUTUAL_FLAG)) ++ fatal("Mutual authentication failed"); ++ ++ /* If integ avail flag is not true kex fails */ ++ if (!(ret_flags & GSS_C_INTEG_FLAG)) ++ fatal("Integrity check failed"); ++ } ++ ++ /* ++ * If we have data to send, then the last message that we ++ * received cannot have been a 'complete'. ++ */ ++ if (send_tok.length != 0) { ++ if (gss->first) { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || ++ (r = sshpkt_put_stringb(ssh, kex->client_pub)) != 0) ++ fatal("failed to construct packet: %s", ssh_err(r)); ++ gss->first = 0; ++ } else { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) ++ fatal("failed to construct packet: %s", ssh_err(r)); ++ } ++ if ((r = sshpkt_send(ssh)) != 0) ++ fatal("failed to send packet: %s", ssh_err(r)); ++ gss_release_buffer(&gss->minor, &send_tok); ++ ++ /* If we've sent them data, they should reply */ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, &input_kexgss_hostkey); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgss_continue); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, &input_kexgss_complete); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, &input_kexgss_error); ++ return 0; ++ } ++ /* No data, and not complete */ ++ if (gss->major != GSS_S_COMPLETE) ++ fatal("Not complete, and no token output"); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return kexgss_init_ctx(ssh, token_ptr); ++ ++ return kexgss_final(ssh); ++} ++ ++int ++kexgss_client(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ int r; ++ ++ /* Initialise our GSSAPI world */ ++ ssh_gssapi_build_ctx(&kex->gss); ++ if (ssh_gssapi_id_kex(kex->gss, kex->name, kex->kex_type) == GSS_C_NO_OID) ++ fatal("Couldn't identify host exchange"); ++ ++ if (ssh_gssapi_import_name(kex->gss, kex->gss_host)) ++ fatal("Couldn't import hostname"); ++ ++ if (kex->gss_client && ++ ssh_gssapi_client_identity(kex->gss, kex->gss_client)) ++ fatal("Couldn't acquire client credentials"); ++ ++ /* Step 1 */ ++ switch (kex->kex_type) { ++ case KEX_GSS_GRP1_SHA1: ++ case KEX_GSS_GRP14_SHA1: ++ case KEX_GSS_GRP14_SHA256: ++ case KEX_GSS_GRP16_SHA512: ++ r = kex_dh_keypair(kex); ++ break; ++ case KEX_GSS_NISTP256_SHA256: ++ r = kex_ecdh_keypair(kex); ++ break; ++ case KEX_GSS_C25519_SHA256: ++ r = kex_c25519_keypair(kex); ++ break; ++ default: ++ fatal_f("Unexpected KEX type %d", kex->kex_type); ++ } ++ if (r != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ return r; ++ } ++ return kexgss_init_ctx(ssh, GSS_C_NO_BUFFER); ++} ++ ++static int ++input_kexgss_hostkey(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ u_char *tmp = NULL; ++ size_t tmp_len = 0; ++ int r; ++ ++ debug("Received KEXGSS_HOSTKEY"); ++ if (gss->server_host_key_blob) ++ fatal("Server host key received more than once"); ++ if ((r = sshpkt_get_string(ssh, &tmp, &tmp_len)) != 0) ++ fatal("Failed to read server host key: %s", ssh_err(r)); ++ if ((gss->server_host_key_blob = sshbuf_from(tmp, tmp_len)) == NULL) ++ fatal("sshbuf_from failed"); ++ return 0; ++} ++ ++static int ++input_kexgss_continue(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); ++ ++ debug("Received GSSAPI_CONTINUE"); ++ if (gss->major == GSS_S_COMPLETE) ++ fatal("GSSAPI Continue received from server when complete"); ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("Failed to read token: %s", ssh_err(r)); ++ if (!(gss->major & GSS_S_CONTINUE_NEEDED)) ++ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); ++ return kexgss_init_ctx(ssh, &recv_tok); ++} ++ ++static int ++input_kexgss_complete(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ u_char c; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); ++ ++ debug("Received GSSAPI_COMPLETE"); ++ if (gss->msg_tok.value != NULL) ++ fatal("Received GSSAPI_COMPLETE twice?"); ++ if ((r = sshpkt_getb_froms(ssh, &gss->server_blob)) != 0 || ++ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &gss->msg_tok)) != 0) ++ fatal("Failed to read message: %s", ssh_err(r)); ++ ++ /* Is there a token included? */ ++ if ((r = sshpkt_get_u8(ssh, &c)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ if (c) { ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0) ++ fatal("Failed to read token: %s", ssh_err(r)); ++ /* If we're already complete - protocol error */ ++ if (gss->major == GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: received token when complete"); ++ } else { ++ if (gss->major != GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); ++ } ++ if ((r = sshpkt_get_end(ssh)) != 0) ++ fatal("Expecting end of packet."); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return kexgss_init_ctx(ssh, &recv_tok); ++ ++ gss_release_buffer(&gss->minor, &recv_tok); ++ return kexgss_final(ssh); ++} ++ ++static int ++input_kexgss_error(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ u_char *msg; ++ int r; ++ ++ debug("Received Error"); ++ if ((r = sshpkt_get_u32(ssh, &gss->major)) != 0 || ++ (r = sshpkt_get_u32(ssh, &gss->minor)) != 0 || ++ (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 || ++ (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */ ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt_get failed: %s", ssh_err(r)); ++ fatal("GSSAPI Error: \n%.400s", msg); ++ return 0; ++} ++ ++/*******************************************************/ ++/******************** KEXGSSGEX ************************/ ++/*******************************************************/ ++ ++int ++kexgssgex_client(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ int r; ++ ++ /* Initialise our GSSAPI world */ ++ ssh_gssapi_build_ctx(&kex->gss); ++ if (ssh_gssapi_id_kex(kex->gss, kex->name, kex->kex_type) == GSS_C_NO_OID) ++ fatal("Couldn't identify host exchange"); ++ ++ if (ssh_gssapi_import_name(kex->gss, kex->gss_host)) ++ fatal("Couldn't import hostname"); ++ ++ if (kex->gss_client && ++ ssh_gssapi_client_identity(kex->gss, kex->gss_client)) ++ fatal("Couldn't acquire client credentials"); ++ ++ debug("Doing group exchange"); ++ kex->min = DH_GRP_MIN; ++ kex->max = DH_GRP_MAX; ++ kex->nbits = dh_estimate(kex->dh_need * 8); ++ ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUPREQ)) != 0 || ++ (r = sshpkt_put_u32(ssh, kex->min)) != 0 || ++ (r = sshpkt_put_u32(ssh, kex->nbits)) != 0 || ++ (r = sshpkt_put_u32(ssh, kex->max)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("Failed to construct a packet: %s", ssh_err(r)); ++ ++ debug("Wait SSH2_MSG_KEXGSS_GROUP"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUP, &input_kexgssgex_group); ++ return 0; ++} ++ ++static int ++kexgssgex_final(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ struct sshbuf *buf = NULL; ++ struct sshbuf *empty = NULL; ++ struct sshbuf *shared_secret = NULL; ++ BIGNUM *dh_server_pub = NULL; ++ const BIGNUM *pub_key, *dh_p, *dh_g; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ int r = SSH_ERR_INTERNAL_ERROR; ++ ++ /* ++ * We _must_ have received a COMPLETE message in reply from the ++ * server, which will have set server_blob and msg_tok ++ */ ++ ++ /* 7. C verifies that the key Q_S is valid */ ++ /* 8. C computes shared secret */ ++ if ((buf = sshbuf_new()) == NULL || ++ (r = sshbuf_put_stringb(buf, gss->server_blob)) != 0 || ++ (r = sshbuf_get_bignum2(buf, &dh_server_pub)) != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ goto out; ++ } ++ sshbuf_free(buf); ++ buf = NULL; ++ ++ if ((shared_secret = sshbuf_new()) == NULL) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ goto out; ++ } ++ ++ if ((empty = sshbuf_new()) == NULL) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ DH_get0_key(kex->dh, &pub_key, NULL); ++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); ++ hashlen = sizeof(hash); ++ r = kexgex_hash(kex->hash_alg, kex->client_version, ++ kex->server_version, kex->my, kex->peer, ++ (gss->server_host_key_blob ? gss->server_host_key_blob : empty), ++ kex->min, kex->nbits, kex->max, dh_p, dh_g, pub_key, ++ dh_server_pub, sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), ++ hash, &hashlen); ++ sshbuf_free(empty); ++ if (r != 0) ++ fatal("Failed to calculate hash: %s", ssh_err(r)); ++ ++ gss->buf.value = hash; ++ gss->buf.length = hashlen; ++ ++ /* Verify that the hash matches the MIC we just got. */ ++ if (GSS_ERROR(ssh_gssapi_checkmic(gss, &gss->buf, &gss->msg_tok))) ++ sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); ++ ++ gss_release_buffer(&gss->minor, &gss->msg_tok); ++ ++ if (kex->gss_deleg_creds) ++ ssh_gssapi_credentials_updated(gss); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = gss; ++ else ++ ssh_gssapi_delete_ctx(&kex->gss); ++ ++ /* Finally derive the keys and send them */ ++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) ++ r = kex_send_newkeys(ssh); ++ ++ if (kex->gss != NULL) { ++ sshbuf_free(gss->server_host_key_blob); ++ gss->server_host_key_blob = NULL; ++ sshbuf_free(gss->server_blob); ++ gss->server_blob = NULL; ++ } ++out: ++ explicit_bzero(hash, sizeof(hash)); ++ DH_free(kex->dh); ++ kex->dh = NULL; ++ BN_clear_free(dh_server_pub); ++ sshbuf_free(shared_secret); ++ return r; ++} ++ ++static int ++kexgssgex_init_ctx(struct ssh *ssh, ++ gss_buffer_desc *token_ptr) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ const BIGNUM *pub_key; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags; ++ int r; ++ ++ /* Step 2 - call GSS_Init_sec_context() */ ++ debug("Calling gss_init_sec_context"); ++ ++ gss->major = ssh_gssapi_init_ctx(gss, kex->gss_deleg_creds, ++ token_ptr, &send_tok, &ret_flags); ++ ++ if (GSS_ERROR(gss->major)) { ++ /* XXX Useless code: Missing send? */ ++ if (send_tok.length != 0) { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ fatal("gss_init_context failed"); ++ } ++ ++ /* If we've got an old receive buffer get rid of it */ ++ if (token_ptr != GSS_C_NO_BUFFER) ++ gss_release_buffer(&gss->minor, token_ptr); ++ ++ if (gss->major == GSS_S_COMPLETE) { ++ /* If mutual state flag is not true, kex fails */ ++ if (!(ret_flags & GSS_C_MUTUAL_FLAG)) ++ fatal("Mutual authentication failed"); ++ ++ /* If integ avail flag is not true kex fails */ ++ if (!(ret_flags & GSS_C_INTEG_FLAG)) ++ fatal("Integrity check failed"); ++ } ++ ++ /* ++ * If we have data to send, then the last message that we ++ * received cannot have been a 'complete'. ++ */ ++ if (send_tok.length != 0) { ++ if (gss->first) { ++ DH_get0_key(kex->dh, &pub_key, NULL); ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || ++ (r = sshpkt_put_bignum2(ssh, pub_key)) != 0) ++ fatal("failed to construct packet: %s", ssh_err(r)); ++ gss->first = 0; ++ } else { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) ++ fatal("failed to construct packet: %s", ssh_err(r)); ++ } ++ if ((r = sshpkt_send(ssh)) != 0) ++ fatal("failed to send packet: %s", ssh_err(r)); ++ gss_release_buffer(&gss->minor, &send_tok); ++ ++ /* If we've sent them data, they should reply */ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, &input_kexgss_hostkey); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgssgex_continue); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, &input_kexgssgex_complete); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, &input_kexgss_error); ++ return 0; ++ } ++ /* No data, and not complete */ ++ if (gss->major != GSS_S_COMPLETE) ++ fatal("Not complete, and no token output"); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return kexgssgex_init_ctx(ssh, token_ptr); ++ ++ return kexgssgex_final(ssh); ++} ++ ++static int ++input_kexgssgex_group(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ BIGNUM *p = NULL; ++ BIGNUM *g = NULL; ++ int r; ++ ++ debug("Received SSH2_MSG_KEXGSS_GROUP"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUP, NULL); ++ ++ if ((r = sshpkt_get_bignum2(ssh, &p)) != 0 || ++ (r = sshpkt_get_bignum2(ssh, &g)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("shpkt_get_bignum2 failed: %s", ssh_err(r)); ++ ++ if (BN_num_bits(p) < kex->min || BN_num_bits(p) > kex->max) ++ fatal("GSSGRP_GEX group out of range: %d !< %d !< %d", ++ kex->min, BN_num_bits(p), kex->max); ++ ++ if ((kex->dh = dh_new_group(g, p)) == NULL) ++ fatal("dn_new_group() failed"); ++ p = g = NULL; /* belong to kex->dh now */ ++ ++ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ DH_free(kex->dh); ++ kex->dh = NULL; ++ return r; ++ } ++ ++ return kexgssgex_init_ctx(ssh, GSS_C_NO_BUFFER); ++} ++ ++static int ++input_kexgssgex_continue(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); ++ ++ debug("Received GSSAPI_CONTINUE"); ++ if (gss->major == GSS_S_COMPLETE) ++ fatal("GSSAPI Continue received from server when complete"); ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("Failed to read token: %s", ssh_err(r)); ++ if (!(gss->major & GSS_S_CONTINUE_NEEDED)) ++ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); ++ return kexgssgex_init_ctx(ssh, &recv_tok); ++} ++ ++static int ++input_kexgssgex_complete(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ u_char c; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); ++ ++ debug("Received GSSAPI_COMPLETE"); ++ if (gss->msg_tok.value != NULL) ++ fatal("Received GSSAPI_COMPLETE twice?"); ++ if ((r = sshpkt_getb_froms(ssh, &gss->server_blob)) != 0 || ++ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &gss->msg_tok)) != 0) ++ fatal("Failed to read message: %s", ssh_err(r)); ++ ++ /* Is there a token included? */ ++ if ((r = sshpkt_get_u8(ssh, &c)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ if (c) { ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0) ++ fatal("Failed to read token: %s", ssh_err(r)); ++ /* If we're already complete - protocol error */ ++ if (gss->major == GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: received token when complete"); ++ } else { ++ if (gss->major != GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); ++ } ++ if ((r = sshpkt_get_end(ssh)) != 0) ++ fatal("Expecting end of packet."); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return kexgssgex_init_ctx(ssh, &recv_tok); ++ ++ gss_release_buffer(&gss->minor, &recv_tok); ++ return kexgssgex_final(ssh); ++} ++ ++#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ +diff --color -ruNp a/kexgsss.c b/kexgsss.c +--- a/kexgsss.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/kexgsss.c 2024-10-14 15:18:02.491798105 +0200 +@@ -0,0 +1,601 @@ ++/* ++ * Copyright (c) 2001-2009 Simon Wilkinson. 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. ++ * ++ * 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" ++ ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ ++#include ++ ++#include ++#include ++ ++#include "xmalloc.h" ++#include "sshbuf.h" ++#include "ssh2.h" ++#include "sshkey.h" ++#include "cipher.h" ++#include "kex.h" ++#include "log.h" ++#include "packet.h" ++#include "dh.h" ++#include "ssh-gss.h" ++#include "monitor_wrap.h" ++#include "misc.h" /* servconf.h needs misc.h for struct ForwardOptions */ ++#include "servconf.h" ++#include "ssh-gss.h" ++#include "digest.h" ++#include "ssherr.h" ++ ++extern ServerOptions options; ++ ++static int input_kexgss_init(int, u_int32_t, struct ssh *); ++static int input_kexgss_continue(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_groupreq(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_init(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_continue(int, u_int32_t, struct ssh *); ++ ++int ++kexgss_server(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ gss_OID oid; ++ char *mechs; ++ ++ /* Initialise GSSAPI */ ++ ++ /* If we're rekeying, privsep means that some of the private structures ++ * in the GSSAPI code are no longer available. This kludges them back ++ * into life ++ */ ++ if (!ssh_gssapi_oid_table_ok()) { ++ mechs = ssh_gssapi_server_mechanisms(); ++ free(mechs); ++ } ++ ++ debug2_f("Identifying %s", kex->name); ++ oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type); ++ if (oid == GSS_C_NO_OID) ++ fatal("Unknown gssapi mechanism"); ++ ++ debug2_f("Acquiring credentials"); ++ ++ if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, oid))) ++ fatal("Unable to acquire credentials for the server"); ++ ++ ssh_gssapi_build_ctx(&kex->gss); ++ if (kex->gss == NULL) ++ fatal("Unable to allocate memory for gss context"); ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, &input_kexgss_init); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgss_continue); ++ debug("Wait SSH2_MSG_KEXGSS_INIT"); ++ return 0; ++} ++ ++static inline void ++kexgss_accept_ctx(struct ssh *ssh, ++ gss_buffer_desc *recv_tok, ++ gss_buffer_desc *send_tok, ++ OM_uint32 *ret_flags) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ int r; ++ ++ gss->major = mm_ssh_gssapi_accept_ctx(gss, recv_tok, send_tok, ret_flags); ++ gss_release_buffer(&gss->minor, recv_tok); ++ ++ if (gss->major != GSS_S_COMPLETE && send_tok->length == 0) ++ fatal("Zero length token output when incomplete"); ++ ++ if (gss->buf.value == NULL) ++ fatal("No client public key"); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) { ++ debug("Sending GSSAPI_CONTINUE"); ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ gss_release_buffer(&gss->minor, send_tok); ++ } ++} ++ ++static inline int ++kexgss_final(struct ssh *ssh, ++ gss_buffer_desc *send_tok, ++ OM_uint32 *ret_flags) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ gss_buffer_desc msg_tok; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ struct sshbuf *shared_secret = NULL; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ++ if (GSS_ERROR(gss->major)) { ++ if (send_tok->length > 0) { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ fatal("accept_ctx died"); ++ } ++ ++ if (!(*ret_flags & GSS_C_MUTUAL_FLAG)) ++ fatal("Mutual Authentication flag wasn't set"); ++ ++ if (!(*ret_flags & GSS_C_INTEG_FLAG)) ++ fatal("Integrity flag wasn't set"); ++ ++ if (GSS_ERROR(mm_ssh_gssapi_sign(gss, &gss->buf, &msg_tok))) ++ fatal("Couldn't get MIC"); ++ ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 || ++ (r = sshpkt_put_stringb(ssh, gss->server_pubkey)) != 0 || ++ (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ if (send_tok->length != 0) { ++ if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */ ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } else { ++ if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */ ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ if ((r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt_send failed: %s", ssh_err(r)); ++ ++ gss_release_buffer(&gss->minor, send_tok); ++ gss_release_buffer(&gss->minor, &msg_tok); ++ ++ hashlen = gss->hashlen; ++ memcpy(hash, gss->hash, hashlen); ++ explicit_bzero(gss->hash, sizeof(gss->hash)); ++ shared_secret = gss->shared_secret; ++ gss->shared_secret = NULL; ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = gss; ++ else ++ ssh_gssapi_delete_ctx(&kex->gss); ++ ++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) ++ r = kex_send_newkeys(ssh); ++ ++ /* If this was a rekey, then save out any delegated credentials we ++ * just exchanged. */ ++ if (options.gss_store_rekey) ++ ssh_gssapi_rekey_creds(); ++ ++ if (kex->gss != NULL) { ++ sshbuf_free(gss->server_pubkey); ++ gss->server_pubkey = NULL; ++ } ++ explicit_bzero(hash, sizeof(hash)); ++ sshbuf_free(shared_secret); ++ return r; ++} ++ ++static int ++input_kexgss_init(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ struct sshbuf *empty; ++ struct sshbuf *client_pubkey = NULL; ++ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags = 0; ++ int r; ++ ++ debug("SSH2_MSG_KEXGSS_INIT received"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); ++ ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_getb_froms(ssh, &client_pubkey)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ switch (kex->kex_type) { ++ case KEX_GSS_GRP1_SHA1: ++ case KEX_GSS_GRP14_SHA1: ++ case KEX_GSS_GRP14_SHA256: ++ case KEX_GSS_GRP16_SHA512: ++ r = kex_dh_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret); ++ break; ++ case KEX_GSS_NISTP256_SHA256: ++ r = kex_ecdh_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret); ++ break; ++ case KEX_GSS_C25519_SHA256: ++ r = kex_c25519_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret); ++ break; ++ default: ++ fatal_f("Unexpected KEX type %d", kex->kex_type); ++ } ++ if (r != 0) { ++ sshbuf_free(client_pubkey); ++ gss_release_buffer(&gss->minor, &recv_tok); ++ ssh_gssapi_delete_ctx(&kex->gss); ++ return r; ++ } ++ ++ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ ++ ++ if ((empty = sshbuf_new()) == NULL) { ++ sshbuf_free(client_pubkey); ++ gss_release_buffer(&gss->minor, &recv_tok); ++ ssh_gssapi_delete_ctx(&kex->gss); ++ return SSH_ERR_ALLOC_FAIL; ++ } ++ ++ /* Calculate the hash early so we can free the ++ * client_pubkey, which has reference to the parent ++ * buffer state->incoming_packet ++ */ ++ gss->hashlen = sizeof(gss->hash); ++ r = kex_gen_hash(kex->hash_alg, kex->client_version, kex->server_version, ++ kex->peer, kex->my, empty, client_pubkey, gss->server_pubkey, ++ gss->shared_secret, gss->hash, &gss->hashlen); ++ sshbuf_free(empty); ++ sshbuf_free(client_pubkey); ++ if (r != 0) { ++ gss_release_buffer(&gss->minor, &recv_tok); ++ ssh_gssapi_delete_ctx(&kex->gss); ++ return r; ++ } ++ ++ gss->buf.value = gss->hash; ++ gss->buf.length = gss->hashlen; ++ ++ kexgss_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return 0; ++ ++ return kexgss_final(ssh, &send_tok, &ret_flags); ++} ++ ++static int ++input_kexgss_continue(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags = 0; ++ int r; ++ ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ kexgss_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return 0; ++ ++ return kexgss_final(ssh, &send_tok, &ret_flags); ++} ++ ++/*******************************************************/ ++/******************** KEXGSSGEX ************************/ ++/*******************************************************/ ++ ++int ++kexgssgex_server(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ gss_OID oid; ++ char *mechs; ++ ++ /* Initialise GSSAPI */ ++ ++ /* If we're rekeying, privsep means that some of the private structures ++ * in the GSSAPI code are no longer available. This kludges them back ++ * into life ++ */ ++ if (!ssh_gssapi_oid_table_ok()) { ++ mechs = ssh_gssapi_server_mechanisms(); ++ free(mechs); ++ } ++ ++ debug2_f("Identifying %s", kex->name); ++ oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type); ++ if (oid == GSS_C_NO_OID) ++ fatal("Unknown gssapi mechanism"); ++ ++ debug2_f("Acquiring credentials"); ++ ++ if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, oid))) ++ fatal("Unable to acquire credentials for the server"); ++ ++ ssh_gssapi_build_ctx(&kex->gss); ++ if (kex->gss == NULL) ++ fatal("Unable to allocate memory for gss context"); ++ ++ debug("Doing group exchange"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUPREQ, &input_kexgssgex_groupreq); ++ return 0; ++} ++ ++static inline void ++kexgssgex_accept_ctx(struct ssh *ssh, ++ gss_buffer_desc *recv_tok, ++ gss_buffer_desc *send_tok, ++ OM_uint32 *ret_flags) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ int r; ++ ++ gss->major = mm_ssh_gssapi_accept_ctx(gss, recv_tok, send_tok, ret_flags); ++ gss_release_buffer(&gss->minor, recv_tok); ++ ++ if (gss->major != GSS_S_COMPLETE && send_tok->length == 0) ++ fatal("Zero length token output when incomplete"); ++ ++ if (gss->dh_client_pub == NULL) ++ fatal("No client public key"); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) { ++ debug("Sending GSSAPI_CONTINUE"); ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ gss_release_buffer(&gss->minor, send_tok); ++ } ++} ++ ++static inline int ++kexgssgex_final(struct ssh *ssh, ++ gss_buffer_desc *send_tok, ++ OM_uint32 *ret_flags) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ gss_buffer_desc msg_tok; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ const BIGNUM *pub_key, *dh_p, *dh_g; ++ struct sshbuf *shared_secret = NULL; ++ struct sshbuf *empty = NULL; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ++ if (GSS_ERROR(gss->major)) { ++ if (send_tok->length > 0) { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ fatal("accept_ctx died"); ++ } ++ ++ if (!(*ret_flags & GSS_C_MUTUAL_FLAG)) ++ fatal("Mutual Authentication flag wasn't set"); ++ ++ if (!(*ret_flags & GSS_C_INTEG_FLAG)) ++ fatal("Integrity flag wasn't set"); ++ ++ /* calculate shared secret */ ++ shared_secret = sshbuf_new(); ++ if (shared_secret == NULL) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ if ((r = kex_dh_compute_key(kex, gss->dh_client_pub, shared_secret)) != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ goto out; ++ } ++ ++ if ((empty = sshbuf_new()) == NULL) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ DH_get0_key(kex->dh, &pub_key, NULL); ++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); ++ hashlen = sizeof(hash); ++ r = kexgex_hash(kex->hash_alg, kex->client_version, kex->server_version, ++ kex->peer, kex->my, empty, kex->min, kex->nbits, kex->max, dh_p, dh_g, ++ gss->dh_client_pub, pub_key, sshbuf_ptr(shared_secret), ++ sshbuf_len(shared_secret), hash, &hashlen); ++ sshbuf_free(empty); ++ if (r != 0) ++ fatal("kexgex_hash failed: %s", ssh_err(r)); ++ ++ gss->buf.value = hash; ++ gss->buf.length = hashlen; ++ ++ if (GSS_ERROR(mm_ssh_gssapi_sign(gss, &gss->buf, &msg_tok))) ++ fatal("Couldn't get MIC"); ++ ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 || ++ (r = sshpkt_put_bignum2(ssh, pub_key)) != 0 || ++ (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ if (send_tok->length != 0) { ++ if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */ ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } else { ++ if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */ ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ if ((r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt_send failed: %s", ssh_err(r)); ++ ++ gss_release_buffer(&gss->minor, send_tok); ++ gss_release_buffer(&gss->minor, &msg_tok); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = gss; ++ else ++ ssh_gssapi_delete_ctx(&kex->gss); ++ ++ /* Finally derive the keys and send them */ ++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) ++ r = kex_send_newkeys(ssh); ++ ++ /* If this was a rekey, then save out any delegated credentials we ++ * just exchanged. */ ++ if (options.gss_store_rekey) ++ ssh_gssapi_rekey_creds(); ++ ++ if (kex->gss != NULL) ++ BN_clear_free(gss->dh_client_pub); ++ ++out: ++ explicit_bzero(hash, sizeof(hash)); ++ DH_free(kex->dh); ++ kex->dh = NULL; ++ sshbuf_free(shared_secret); ++ return r; ++} ++ ++static int ++input_kexgssgex_groupreq(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ const BIGNUM *dh_p, *dh_g; ++ int min = -1, max = -1, nbits = -1; ++ int cmin = -1, cmax = -1; /* client proposal */ ++ int r; ++ ++ /* 5. S generates an ephemeral key pair (do the allocations early) */ ++ ++ debug("SSH2_MSG_KEXGSS_GROUPREQ received"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUPREQ, NULL); ++ ++ /* store client proposal to provide valid signature */ ++ if ((r = sshpkt_get_u32(ssh, &cmin)) != 0 || ++ (r = sshpkt_get_u32(ssh, &nbits)) != 0 || ++ (r = sshpkt_get_u32(ssh, &cmax)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ kex->nbits = nbits; ++ kex->min = cmin; ++ kex->max = cmax; ++ min = MAX(DH_GRP_MIN, cmin); ++ max = MIN(DH_GRP_MAX, cmax); ++ nbits = MAXIMUM(DH_GRP_MIN, nbits); ++ nbits = MINIMUM(DH_GRP_MAX, nbits); ++ ++ if (max < min || nbits < min || max < nbits) ++ fatal("GSS_GEX, bad parameters: %d !< %d !< %d", min, nbits, max); ++ ++ kex->dh = mm_choose_dh(min, nbits, max); ++ if (kex->dh == NULL) { ++ sshpkt_disconnect(ssh, "Protocol error: no matching group found"); ++ fatal("Protocol error: no matching group found"); ++ } ++ ++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 || ++ (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 || ++ (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ if ((r = ssh_packet_write_wait(ssh)) != 0) ++ fatal("ssh_packet_write_wait: %s", ssh_err(r)); ++ ++ /* Compute our exchange value in parallel with the client */ ++ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ DH_free(kex->dh); ++ kex->dh = NULL; ++ return r; ++ } ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, &input_kexgssgex_init); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgssgex_continue); ++ debug("Wait SSH2_MSG_KEXGSS_INIT"); ++ return 0; ++} ++ ++static int ++input_kexgssgex_init(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags = 0; ++ int r; ++ ++ debug("SSH2_MSG_KEXGSS_INIT received"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); ++ ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_get_bignum2(ssh, &gss->dh_client_pub)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ ++ ++ kexgssgex_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return 0; ++ ++ return kexgssgex_final(ssh, &send_tok, &ret_flags); ++} ++ ++static int ++input_kexgssgex_continue(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags = 0; ++ int r; ++ ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ kexgssgex_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return 0; ++ ++ return kexgssgex_final(ssh, &send_tok, &ret_flags); ++} ++ ++#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ +diff --color -ruNp a/kex.h b/kex.h +--- a/kex.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/kex.h 2024-09-16 11:46:34.710940224 +0200 +@@ -29,6 +29,10 @@ + #include "mac.h" + #include "crypto_api.h" + ++#ifdef GSSAPI ++# include "ssh-gss.h" /* Gssctxt */ ++#endif ++ + #ifdef WITH_OPENSSL + # include + # include +@@ -102,6 +106,15 @@ enum kex_exchange { + KEX_C25519_SHA256, + KEX_KEM_SNTRUP761X25519_SHA512, + KEX_KEM_MLKEM768X25519_SHA256, ++#ifdef GSSAPI ++ KEX_GSS_GRP1_SHA1, ++ KEX_GSS_GRP14_SHA1, ++ KEX_GSS_GRP14_SHA256, ++ KEX_GSS_GRP16_SHA512, ++ KEX_GSS_GEX_SHA1, ++ KEX_GSS_NISTP256_SHA256, ++ KEX_GSS_C25519_SHA256, ++#endif + KEX_MAX + }; + +@@ -164,6 +177,13 @@ struct kex { + u_int flags; + int hash_alg; + int ec_nid; ++#ifdef GSSAPI ++ Gssctxt *gss; ++ int gss_deleg_creds; ++ int gss_trust_dns; ++ char *gss_host; ++ char *gss_client; ++#endif + char *failed_choice; + int (*verify_host_key)(struct sshkey *, struct ssh *); + struct sshkey *(*load_host_public_key)(int, int, struct ssh *); +@@ -189,8 +209,10 @@ int kex_hash_from_name(const char *); + int kex_nid_from_name(const char *); + int kex_names_valid(const char *); + char *kex_alg_list(char); ++char *kex_gss_alg_list(char); + char *kex_names_cat(const char *, const char *); + int kex_has_any_alg(const char *, const char *); ++int kex_gss_names_valid(const char *); + int kex_assemble_names(char **, const char *, const char *); + void kex_proposal_populate_entries(struct ssh *, char *prop[PROPOSAL_MAX], + const char *, const char *, const char *, const char *, const char *); +@@ -224,6 +246,12 @@ int kexgex_client(struct ssh *); + int kexgex_server(struct ssh *); + int kex_gen_client(struct ssh *); + int kex_gen_server(struct ssh *); ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++int kexgssgex_client(struct ssh *); ++int kexgssgex_server(struct ssh *); ++int kexgss_client(struct ssh *); ++int kexgss_server(struct ssh *); ++#endif + + int kex_dh_keypair(struct kex *); + int kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **, +@@ -256,6 +284,12 @@ int kexgex_hash(int, const struct sshbu + const BIGNUM *, const u_char *, size_t, + u_char *, size_t *); + ++int kex_gen_hash(int hash_alg, const struct sshbuf *client_version, ++ const struct sshbuf *server_version, const struct sshbuf *client_kexinit, ++ const struct sshbuf *server_kexinit, const struct sshbuf *server_host_key_blob, ++ const struct sshbuf *client_pub, const struct sshbuf *server_pub, ++ const struct sshbuf *shared_secret, u_char *hash, size_t *hashlen); ++ + void kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE]) + __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) + __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); +diff --color -ruNp a/kex-names.c b/kex-names.c +--- a/kex-names.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/kex-names.c 2024-09-16 11:46:34.694939883 +0200 +@@ -45,6 +45,10 @@ + #include "ssherr.h" + #include "xmalloc.h" + ++#ifdef GSSAPI ++#include "ssh-gss.h" ++#endif ++ + struct kexalg { + char *name; + u_int type; +@@ -83,15 +87,28 @@ static const struct kexalg kexalgs[] = { + #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */ + { NULL, 0, -1, -1}, + }; ++static const struct kexalg gss_kexalgs[] = { ++#ifdef GSSAPI ++ { KEX_GSS_GEX_SHA1_ID, KEX_GSS_GEX_SHA1, 0, SSH_DIGEST_SHA1 }, ++ { KEX_GSS_GRP1_SHA1_ID, KEX_GSS_GRP1_SHA1, 0, SSH_DIGEST_SHA1 }, ++ { KEX_GSS_GRP14_SHA1_ID, KEX_GSS_GRP14_SHA1, 0, SSH_DIGEST_SHA1 }, ++ { KEX_GSS_GRP14_SHA256_ID, KEX_GSS_GRP14_SHA256, 0, SSH_DIGEST_SHA256 }, ++ { KEX_GSS_GRP16_SHA512_ID, KEX_GSS_GRP16_SHA512, 0, SSH_DIGEST_SHA512 }, ++ { KEX_GSS_NISTP256_SHA256_ID, KEX_GSS_NISTP256_SHA256, ++ NID_X9_62_prime256v1, SSH_DIGEST_SHA256 }, ++ { KEX_GSS_C25519_SHA256_ID, KEX_GSS_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, ++#endif ++ { NULL, 0, -1, -1}, ++}; + +-char * +-kex_alg_list(char sep) ++static char * ++kex_alg_list_internal(char sep, const struct kexalg *algs) + { + char *ret = NULL, *tmp; + size_t nlen, rlen = 0; + const struct kexalg *k; + +- for (k = kexalgs; k->name != NULL; k++) { ++ for (k = algs; k->name != NULL; k++) { + if (ret != NULL) + ret[rlen++] = sep; + nlen = strlen(k->name); +@@ -106,6 +123,18 @@ kex_alg_list(char sep) + return ret; + } + ++char * ++kex_alg_list(char sep) ++{ ++ return kex_alg_list_internal(sep, kexalgs); ++} ++ ++char * ++kex_gss_alg_list(char sep) ++{ ++ return kex_alg_list_internal(sep, gss_kexalgs); ++} ++ + static const struct kexalg * + kex_alg_by_name(const char *name) + { +@@ -115,6 +144,10 @@ kex_alg_by_name(const char *name) + if (strcmp(k->name, name) == 0) + return k; + } ++ for (k = gss_kexalgs; k->name != NULL; k++) { ++ if (strncmp(k->name, name, strlen(k->name)) == 0) ++ return k; ++ } + return NULL; + } + +@@ -328,3 +361,26 @@ kex_assemble_names(char **listp, const c + free(ret); + return r; + } ++ ++/* Validate GSS KEX method name list */ ++int ++kex_gss_names_valid(const char *names) ++{ ++ char *s, *cp, *p; ++ ++ if (names == NULL || *names == '\0') ++ return 0; ++ s = cp = xstrdup(names); ++ for ((p = strsep(&cp, ",")); p && *p != '\0'; ++ (p = strsep(&cp, ","))) { ++ if (strncmp(p, "gss-", 4) != 0 ++ || kex_alg_by_name(p) == NULL) { ++ error("Unsupported KEX algorithm \"%.100s\"", p); ++ free(s); ++ return 0; ++ } ++ } ++ debug3("gss kex names ok: [%s]", names); ++ free(s); ++ return 1; ++} +diff --color -ruNp a/Makefile.in b/Makefile.in +--- a/Makefile.in 2024-09-16 11:45:56.868133454 +0200 ++++ b/Makefile.in 2024-09-16 11:46:34.695939904 +0200 +@@ -114,6 +114,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ + kex.o kex-names.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ + kexgexc.o kexgexs.o \ + kexsntrup761x25519.o kexmlkem768x25519.o sntrup761.o kexgen.o \ ++ kexgssc.o \ + sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ + sshbuf-io.o + +@@ -135,7 +136,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rh + auth2-chall.o groupaccess.o \ + auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ + auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \ +- monitor.o monitor_wrap.o auth-krb5.o \ ++ monitor.o monitor_wrap.o auth-krb5.o kexgsss.o \ + auth2-gss.o gss-serv.o gss-serv-krb5.o \ + loginrec.o auth-pam.o auth-shadow.o auth-sia.o \ + sftp-server.o sftp-common.o \ +@@ -529,7 +530,7 @@ regress-prep: + ln -s `cd $(srcdir) && pwd`/regress/Makefile `pwd`/regress/Makefile + + REGRESSLIBS=libssh.a $(LIBCOMPAT) +-TESTLIBS=$(LIBS) $(CHANNELLIBS) ++TESTLIBS=$(LIBS) $(CHANNELLIBS) $(GSSLIBS) + + regress/modpipe$(EXEEXT): $(srcdir)/regress/modpipe.c $(REGRESSLIBS) + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(srcdir)/regress/modpipe.c \ +diff --color -ruNp a/monitor.c b/monitor.c +--- a/monitor.c 2024-09-16 11:45:56.861133305 +0200 ++++ b/monitor.c 2024-09-16 11:46:34.696939926 +0200 +@@ -143,6 +143,8 @@ int mm_answer_gss_setup_ctx(struct ssh * + int mm_answer_gss_accept_ctx(struct ssh *, int, struct sshbuf *); + int mm_answer_gss_userok(struct ssh *, int, struct sshbuf *); + int mm_answer_gss_checkmic(struct ssh *, int, struct sshbuf *); ++int mm_answer_gss_sign(struct ssh *, int, struct sshbuf *); ++int mm_answer_gss_updatecreds(struct ssh *, int, struct sshbuf *); + #endif + + #ifdef SSH_AUDIT_EVENTS +@@ -219,11 +221,18 @@ struct mon_table mon_dispatch_proto20[] + {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, + {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok}, + {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic}, ++ {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign}, + #endif + {0, 0, NULL} + }; + + struct mon_table mon_dispatch_postauth20[] = { ++#ifdef GSSAPI ++ {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx}, ++ {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, ++ {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign}, ++ {MONITOR_REQ_GSSUPCREDS, 0, mm_answer_gss_updatecreds}, ++#endif + #ifdef WITH_OPENSSL + {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, + #endif +@@ -292,6 +301,10 @@ monitor_child_preauth(struct ssh *ssh, s + /* Permit requests for moduli and signatures */ + monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); ++#ifdef GSSAPI ++ /* and for the GSSAPI key exchange */ ++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); ++#endif + + /* The first few requests do not require asynchronous access */ + while (!authenticated) { +@@ -344,8 +357,15 @@ monitor_child_preauth(struct ssh *ssh, s + if (ent->flags & (MON_AUTHDECIDE|MON_ALOG)) { + auth_log(ssh, authenticated, partial, + auth_method, auth_submethod); +- if (!partial && !authenticated) ++ if (!partial && !authenticated) { ++#ifdef GSSAPI ++ /* If gssapi-with-mic failed, MONITOR_REQ_GSSCHECKMIC is disabled. ++ * We have to reenable it to try again for gssapi-keyex */ ++ if (strcmp(auth_method, "gssapi-with-mic") == 0 && options.gss_keyex) ++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); ++#endif + authctxt->failures++; ++ } + if (authenticated || partial) { + auth2_update_session_info(authctxt, + auth_method, auth_submethod); +@@ -413,6 +433,10 @@ monitor_child_postauth(struct ssh *ssh, + monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); ++#ifdef GSSAPI ++ /* and for the GSSAPI key exchange */ ++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); ++#endif + + if (auth_opts->permit_pty_flag) { + monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1); +@@ -1793,6 +1817,17 @@ monitor_apply_keystate(struct ssh *ssh, + # ifdef OPENSSL_HAS_ECC + kex->kex[KEX_ECDH_SHA2] = kex_gen_server; + # endif ++# ifdef GSSAPI ++ if (options.gss_keyex) { ++ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; ++ kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; ++ kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server; ++ kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server; ++ kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server; ++ kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server; ++ kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server; ++ } ++# endif + #endif /* WITH_OPENSSL */ + kex->kex[KEX_C25519_SHA256] = kex_gen_server; + kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; +@@ -1885,8 +1920,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, + u_char *p; + int r; + +- if (!options.gss_authentication) +- fatal_f("GSSAPI authentication not enabled"); ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); + + if ((r = sshbuf_get_string(m, &p, &len)) != 0) + fatal_fr(r, "parse"); +@@ -1918,8 +1953,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh + OM_uint32 flags = 0; /* GSI needs this */ + int r; + +- if (!options.gss_authentication) +- fatal_f("GSSAPI authentication not enabled"); ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); + + if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0) + fatal_fr(r, "ssh_gssapi_get_buffer_desc"); +@@ -1939,6 +1974,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); ++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN, 1); + } + return (0); + } +@@ -1950,8 +1986,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, + OM_uint32 ret; + int r; + +- if (!options.gss_authentication) +- fatal_f("GSSAPI authentication not enabled"); ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); + + if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 || + (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0) +@@ -1977,13 +2013,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, + int + mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) + { +- int r, authenticated; ++ int r, authenticated, kex; + const char *displayname; + +- if (!options.gss_authentication) +- fatal_f("GSSAPI authentication not enabled"); ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); + +- authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user); ++ if ((r = sshbuf_get_u32(m, &kex)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ authenticated = authctxt->valid && ++ ssh_gssapi_userok(authctxt->user, authctxt->pw, kex); + + sshbuf_reset(m); + if ((r = sshbuf_put_u32(m, authenticated)) != 0) +@@ -1992,7 +2032,11 @@ mm_answer_gss_userok(struct ssh *ssh, in + debug3_f("sending result %d", authenticated); + mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m); + +- auth_method = "gssapi-with-mic"; ++ if (kex) { ++ auth_method = "gssapi-keyex"; ++ } else { ++ auth_method = "gssapi-with-mic"; ++ } + + if ((displayname = ssh_gssapi_displayname()) != NULL) + auth2_record_info(authctxt, "%s", displayname); +@@ -2000,5 +2044,84 @@ mm_answer_gss_userok(struct ssh *ssh, in + /* Monitor loop will terminate if authenticated */ + return (authenticated); + } ++ ++int ++mm_answer_gss_sign(struct ssh *ssh, int socket, struct sshbuf *m) ++{ ++ gss_buffer_desc data; ++ gss_buffer_desc hash = GSS_C_EMPTY_BUFFER; ++ OM_uint32 major, minor; ++ size_t len; ++ u_char *p = NULL; ++ int r; ++ ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); ++ ++ if ((r = sshbuf_get_string(m, &p, &len)) != 0) ++ fatal_fr(r, "buffer error"); ++ data.value = p; ++ data.length = len; ++ /* Lengths of SHA-1, SHA-256 and SHA-512 hashes that are used */ ++ if (data.length != 20 && data.length != 32 && data.length != 64) ++ fatal_f("data length incorrect: %d", (int) data.length); ++ ++ /* Save the session ID on the first time around */ ++ if (session_id2_len == 0) { ++ session_id2_len = data.length; ++ session_id2 = xmalloc(session_id2_len); ++ memcpy(session_id2, data.value, session_id2_len); ++ } ++ major = ssh_gssapi_sign(gsscontext, &data, &hash); ++ ++ free(data.value); ++ ++ sshbuf_reset(m); ++ ++ if ((r = sshbuf_put_u32(m, major)) != 0 || ++ (r = sshbuf_put_string(m, hash.value, hash.length)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ mm_request_send(socket, MONITOR_ANS_GSSSIGN, m); ++ ++ gss_release_buffer(&minor, &hash); ++ ++ /* Turn on getpwnam permissions */ ++ monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1); ++ ++ /* And credential updating, for when rekeying */ ++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSUPCREDS, 1); ++ ++ return (0); ++} ++ ++int ++mm_answer_gss_updatecreds(struct ssh *ssh, int socket, struct sshbuf *m) { ++ ssh_gssapi_ccache store; ++ int r, ok; ++ ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); ++ ++ if ((r = sshbuf_get_string(m, (u_char **)&store.filename, NULL)) != 0 || ++ (r = sshbuf_get_string(m, (u_char **)&store.envvar, NULL)) != 0 || ++ (r = sshbuf_get_string(m, (u_char **)&store.envval, NULL)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ ok = ssh_gssapi_update_creds(&store); ++ ++ free(store.filename); ++ free(store.envvar); ++ free(store.envval); ++ ++ sshbuf_reset(m); ++ if ((r = sshbuf_put_u32(m, ok)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ mm_request_send(socket, MONITOR_ANS_GSSUPCREDS, m); ++ ++ return(0); ++} ++ + #endif /* GSSAPI */ + +diff --color -ruNp a/monitor.h b/monitor.h +--- a/monitor.h 2024-09-16 11:45:56.861133305 +0200 ++++ b/monitor.h 2024-09-16 11:46:34.696939926 +0200 +@@ -67,6 +67,8 @@ enum monitor_reqtype { + MONITOR_REQ_PAM_FREE_CTX = 110, MONITOR_ANS_PAM_FREE_CTX = 111, + MONITOR_REQ_AUDIT_EVENT = 112, MONITOR_REQ_AUDIT_COMMAND = 113, + ++ MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151, ++ MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153, + }; + + struct ssh; +diff --color -ruNp a/monitor_wrap.c b/monitor_wrap.c +--- a/monitor_wrap.c 2024-09-16 11:45:56.862133326 +0200 ++++ b/monitor_wrap.c 2024-09-16 11:46:34.697939947 +0200 +@@ -1075,13 +1075,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss + } + + int +-mm_ssh_gssapi_userok(char *user) ++mm_ssh_gssapi_userok(char *user, struct passwd *pw, int kex) + { + struct sshbuf *m; + int r, authenticated = 0; + + if ((m = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); ++ if ((r = sshbuf_put_u32(m, kex)) != 0) ++ fatal_fr(r, "buffer error"); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, m); + mm_request_receive_expect(pmonitor->m_recvfd, +@@ -1094,6 +1096,59 @@ mm_ssh_gssapi_userok(char *user) + debug3_f("user %sauthenticated", authenticated ? "" : "not "); + return (authenticated); + } ++ ++OM_uint32 ++mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash) ++{ ++ struct sshbuf *m; ++ OM_uint32 major; ++ int r; ++ ++ if ((m = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new failed"); ++ if ((r = sshbuf_put_string(m, data->value, data->length)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSIGN, m); ++ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSIGN, m); ++ ++ if ((r = sshbuf_get_u32(m, &major)) != 0 || ++ (r = ssh_gssapi_get_buffer_desc(m, hash)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ sshbuf_free(m); ++ ++ return (major); ++} ++ ++int ++mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *store) ++{ ++ struct sshbuf *m; ++ int r, ok; ++ ++ if ((m = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new failed"); ++ ++ if ((r = sshbuf_put_cstring(m, ++ store->filename ? store->filename : "")) != 0 || ++ (r = sshbuf_put_cstring(m, ++ store->envvar ? store->envvar : "")) != 0 || ++ (r = sshbuf_put_cstring(m, ++ store->envval ? store->envval : "")) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUPCREDS, m); ++ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUPCREDS, m); ++ ++ if ((r = sshbuf_get_u32(m, &ok)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ sshbuf_free(m); ++ ++ return (ok); ++} ++ + #endif /* GSSAPI */ + + /* +diff --color -ruNp a/monitor_wrap.h b/monitor_wrap.h +--- a/monitor_wrap.h 2024-09-16 11:45:56.862133326 +0200 ++++ b/monitor_wrap.h 2024-09-16 11:46:34.697939947 +0200 +@@ -67,8 +67,10 @@ void mm_decode_activate_server_options(s + OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID); + OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *, + gss_buffer_desc *, gss_buffer_desc *, OM_uint32 *); +-int mm_ssh_gssapi_userok(char *user); ++int mm_ssh_gssapi_userok(char *user, struct passwd *, int kex); + OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); ++OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); ++int mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *); + #endif + + #ifdef USE_PAM +diff --color -ruNp a/readconf.c b/readconf.c +--- a/readconf.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/readconf.c 2024-09-16 11:46:34.699939990 +0200 +@@ -70,6 +70,7 @@ + #include "uidswap.h" + #include "myproposal.h" + #include "digest.h" ++#include "ssh-gss.h" + + /* Format of the configuration file: + +@@ -164,6 +165,8 @@ typedef enum { + oClearAllForwardings, oNoHostAuthenticationForLocalhost, + oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, + oAddressFamily, oGssAuthentication, oGssDelegateCreds, ++ oGssTrustDns, oGssKeyEx, oGssClientIdentity, oGssRenewalRekey, ++ oGssServerIdentity, oGssKexAlgorithms, + oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, + oSendEnv, oSetEnv, oControlPath, oControlMaster, oControlPersist, + oHashKnownHosts, +@@ -210,10 +213,22 @@ static struct { + /* Sometimes-unsupported options */ + #if defined(GSSAPI) + { "gssapiauthentication", oGssAuthentication }, ++ { "gssapikeyexchange", oGssKeyEx }, + { "gssapidelegatecredentials", oGssDelegateCreds }, ++ { "gssapitrustdns", oGssTrustDns }, ++ { "gssapiclientidentity", oGssClientIdentity }, ++ { "gssapiserveridentity", oGssServerIdentity }, ++ { "gssapirenewalforcesrekey", oGssRenewalRekey }, ++ { "gssapikexalgorithms", oGssKexAlgorithms }, + # else + { "gssapiauthentication", oUnsupported }, ++ { "gssapikeyexchange", oUnsupported }, + { "gssapidelegatecredentials", oUnsupported }, ++ { "gssapitrustdns", oUnsupported }, ++ { "gssapiclientidentity", oUnsupported }, ++ { "gssapiserveridentity", oUnsupported }, ++ { "gssapirenewalforcesrekey", oUnsupported }, ++ { "gssapikexalgorithms", oUnsupported }, + #endif + #ifdef ENABLE_PKCS11 + { "pkcs11provider", oPKCS11Provider }, +@@ -1227,10 +1242,42 @@ parse_time: + intptr = &options->gss_authentication; + goto parse_flag; + ++ case oGssKeyEx: ++ intptr = &options->gss_keyex; ++ goto parse_flag; ++ + case oGssDelegateCreds: + intptr = &options->gss_deleg_creds; + goto parse_flag; + ++ case oGssTrustDns: ++ intptr = &options->gss_trust_dns; ++ goto parse_flag; ++ ++ case oGssClientIdentity: ++ charptr = &options->gss_client_identity; ++ goto parse_string; ++ ++ case oGssServerIdentity: ++ charptr = &options->gss_server_identity; ++ goto parse_string; ++ ++ case oGssRenewalRekey: ++ intptr = &options->gss_renewal_rekey; ++ goto parse_flag; ++ ++ case oGssKexAlgorithms: ++ arg = argv_next(&ac, &av); ++ if (!arg || *arg == '\0') ++ fatal("%.200s line %d: Missing argument.", ++ filename, linenum); ++ if (!kex_gss_names_valid(arg)) ++ fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.", ++ filename, linenum, arg ? arg : ""); ++ if (*activep && options->gss_kex_algorithms == NULL) ++ options->gss_kex_algorithms = xstrdup(arg); ++ break; ++ + case oBatchMode: + intptr = &options->batch_mode; + goto parse_flag; +@@ -2542,7 +2589,13 @@ initialize_options(Options * options) + options->fwd_opts.streamlocal_bind_unlink = -1; + options->pubkey_authentication = -1; + options->gss_authentication = -1; ++ options->gss_keyex = -1; + options->gss_deleg_creds = -1; ++ options->gss_trust_dns = -1; ++ options->gss_renewal_rekey = -1; ++ options->gss_client_identity = NULL; ++ options->gss_server_identity = NULL; ++ options->gss_kex_algorithms = NULL; + options->password_authentication = -1; + options->kbd_interactive_authentication = -1; + options->kbd_interactive_devices = NULL; +@@ -2705,8 +2758,18 @@ fill_default_options(Options * options) + options->pubkey_authentication = SSH_PUBKEY_AUTH_ALL; + if (options->gss_authentication == -1) + options->gss_authentication = 0; ++ if (options->gss_keyex == -1) ++ options->gss_keyex = 0; + if (options->gss_deleg_creds == -1) + options->gss_deleg_creds = 0; ++ if (options->gss_trust_dns == -1) ++ options->gss_trust_dns = 0; ++ if (options->gss_renewal_rekey == -1) ++ options->gss_renewal_rekey = 0; ++#ifdef GSSAPI ++ if (options->gss_kex_algorithms == NULL) ++ options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX); ++#endif + if (options->password_authentication == -1) + options->password_authentication = 1; + if (options->kbd_interactive_authentication == -1) +@@ -3533,7 +3596,14 @@ dump_client_config(Options *o, const cha + dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports); + #ifdef GSSAPI + dump_cfg_fmtint(oGssAuthentication, o->gss_authentication); ++ dump_cfg_fmtint(oGssKeyEx, o->gss_keyex); + dump_cfg_fmtint(oGssDelegateCreds, o->gss_deleg_creds); ++ dump_cfg_fmtint(oGssTrustDns, o->gss_trust_dns); ++ dump_cfg_fmtint(oGssRenewalRekey, o->gss_renewal_rekey); ++ dump_cfg_string(oGssClientIdentity, o->gss_client_identity); ++ dump_cfg_string(oGssServerIdentity, o->gss_server_identity); ++ dump_cfg_string(oGssKexAlgorithms, o->gss_kex_algorithms ? ++ o->gss_kex_algorithms : GSS_KEX_DEFAULT_KEX); + #endif /* GSSAPI */ + dump_cfg_fmtint(oHashKnownHosts, o->hash_known_hosts); + dump_cfg_fmtint(oHostbasedAuthentication, o->hostbased_authentication); +diff --color -ruNp a/readconf.h b/readconf.h +--- a/readconf.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/readconf.h 2024-09-16 11:46:34.699939990 +0200 +@@ -40,7 +40,13 @@ typedef struct { + int pubkey_authentication; /* Try ssh2 pubkey authentication. */ + int hostbased_authentication; /* ssh2's rhosts_rsa */ + int gss_authentication; /* Try GSS authentication */ ++ int gss_keyex; /* Try GSS key exchange */ + int gss_deleg_creds; /* Delegate GSS credentials */ ++ int gss_trust_dns; /* Trust DNS for GSS canonicalization */ ++ int gss_renewal_rekey; /* Credential renewal forces rekey */ ++ char *gss_client_identity; /* Principal to initiate GSSAPI with */ ++ char *gss_server_identity; /* GSSAPI target principal */ ++ char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */ + int password_authentication; /* Try password + * authentication. */ + int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ +diff --color -ruNp a/servconf.c b/servconf.c +--- a/servconf.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/servconf.c 2024-09-16 11:46:34.700940011 +0200 +@@ -68,6 +68,7 @@ + #include "auth.h" + #include "myproposal.h" + #include "digest.h" ++#include "ssh-gss.h" + + #if !defined(SSHD_PAM_SERVICE) + # define SSHD_PAM_SERVICE "sshd" +@@ -137,8 +138,11 @@ initialize_server_options(ServerOptions + options->kerberos_ticket_cleanup = -1; + options->kerberos_get_afs_token = -1; + options->gss_authentication=-1; ++ options->gss_keyex = -1; + options->gss_cleanup_creds = -1; + options->gss_strict_acceptor = -1; ++ options->gss_store_rekey = -1; ++ options->gss_kex_algorithms = NULL; + options->password_authentication = -1; + options->kbd_interactive_authentication = -1; + options->permit_empty_passwd = -1; +@@ -376,10 +380,18 @@ fill_default_server_options(ServerOption + options->kerberos_get_afs_token = 0; + if (options->gss_authentication == -1) + options->gss_authentication = 0; ++ if (options->gss_keyex == -1) ++ options->gss_keyex = 0; + if (options->gss_cleanup_creds == -1) + options->gss_cleanup_creds = 1; + if (options->gss_strict_acceptor == -1) + options->gss_strict_acceptor = 1; ++ if (options->gss_store_rekey == -1) ++ options->gss_store_rekey = 0; ++#ifdef GSSAPI ++ if (options->gss_kex_algorithms == NULL) ++ options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX); ++#endif + if (options->password_authentication == -1) + options->password_authentication = 1; + if (options->kbd_interactive_authentication == -1) +@@ -558,6 +570,7 @@ typedef enum { + sPerSourcePenalties, sPerSourcePenaltyExemptList, + sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, + sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, ++ sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey, + sAcceptEnv, sSetEnv, sPermitTunnel, + sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, + sUsePrivilegeSeparation, sAllowAgentForwarding, +@@ -643,12 +656,22 @@ static struct { + #ifdef GSSAPI + { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL }, + { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL }, ++ { "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL }, + { "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL }, ++ { "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL }, ++ { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL }, ++ { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL }, + #else + { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, + { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, ++ { "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL }, + { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL }, ++ { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL }, ++ { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL }, ++ { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL }, + #endif ++ { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL }, ++ { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL }, + { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL }, + { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, + { "challengeresponseauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, /* alias */ +@@ -1585,6 +1608,10 @@ process_server_config_line_depth(ServerO + intptr = &options->gss_authentication; + goto parse_flag; + ++ case sGssKeyEx: ++ intptr = &options->gss_keyex; ++ goto parse_flag; ++ + case sGssCleanupCreds: + intptr = &options->gss_cleanup_creds; + goto parse_flag; +@@ -1593,6 +1620,22 @@ process_server_config_line_depth(ServerO + intptr = &options->gss_strict_acceptor; + goto parse_flag; + ++ case sGssStoreRekey: ++ intptr = &options->gss_store_rekey; ++ goto parse_flag; ++ ++ case sGssKexAlgorithms: ++ arg = argv_next(&ac, &av); ++ if (!arg || *arg == '\0') ++ fatal("%.200s line %d: Missing argument.", ++ filename, linenum); ++ if (!kex_gss_names_valid(arg)) ++ fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.", ++ filename, linenum, arg ? arg : ""); ++ if (*activep && options->gss_kex_algorithms == NULL) ++ options->gss_kex_algorithms = xstrdup(arg); ++ break; ++ + case sPasswordAuthentication: + intptr = &options->password_authentication; + goto parse_flag; +@@ -3178,6 +3221,10 @@ dump_config(ServerOptions *o) + #ifdef GSSAPI + dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); + dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); ++ dump_cfg_fmtint(sGssKeyEx, o->gss_keyex); ++ dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor); ++ dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey); ++ dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms); + #endif + dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); + dump_cfg_fmtint(sKbdInteractiveAuthentication, +diff --color -ruNp a/servconf.h b/servconf.h +--- a/servconf.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/servconf.h 2024-09-16 11:46:34.700940011 +0200 +@@ -149,8 +149,11 @@ typedef struct { + int kerberos_get_afs_token; /* If true, try to get AFS token if + * authenticated with Kerberos. */ + int gss_authentication; /* If true, permit GSSAPI authentication */ ++ int gss_keyex; /* If true, permit GSSAPI key exchange */ + int gss_cleanup_creds; /* If true, destroy cred cache on logout */ + int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */ ++ int gss_store_rekey; ++ char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */ + int password_authentication; /* If true, permit password + * authentication. */ + int kbd_interactive_authentication; /* If true, permit */ +diff --color -ruNp a/session.c b/session.c +--- a/session.c 2024-09-16 11:45:56.866133411 +0200 ++++ b/session.c 2024-09-16 11:46:34.701940032 +0200 +@@ -2674,13 +2674,19 @@ do_cleanup(struct ssh *ssh, Authctxt *au + + #ifdef KRB5 + if (options.kerberos_ticket_cleanup && +- authctxt->krb5_ctx) ++ authctxt->krb5_ctx) { ++ temporarily_use_uid(authctxt->pw); + krb5_cleanup_proc(authctxt); ++ restore_uid(); ++ } + #endif + + #ifdef GSSAPI +- if (options.gss_cleanup_creds) ++ if (options.gss_cleanup_creds) { ++ temporarily_use_uid(authctxt->pw); + ssh_gssapi_cleanup_creds(); ++ restore_uid(); ++ } + #endif + + /* remove agent socket */ +diff --color -ruNp a/ssh.1 b/ssh.1 +--- a/ssh.1 2024-09-16 11:45:56.875133603 +0200 ++++ b/ssh.1 2024-09-16 11:46:34.701940032 +0200 +@@ -536,7 +536,13 @@ For full details of the options listed b + .It GatewayPorts + .It GlobalKnownHostsFile + .It GSSAPIAuthentication ++.It GSSAPIKeyExchange ++.It GSSAPIClientIdentity + .It GSSAPIDelegateCredentials ++.It GSSAPIKexAlgorithms ++.It GSSAPIRenewalForcesRekey ++.It GSSAPIServerIdentity ++.It GSSAPITrustDns + .It HashKnownHosts + .It Host + .It HostbasedAcceptedAlgorithms +@@ -624,6 +630,8 @@ flag), + (supported message integrity codes), + .Ar kex + (key exchange algorithms), ++.Ar kex-gss ++(GSSAPI key exchange algorithms), + .Ar key + (key types), + .Ar key-ca-sign +diff --color -ruNp a/ssh.c b/ssh.c +--- a/ssh.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/ssh.c 2024-09-16 11:46:34.702940054 +0200 +@@ -827,6 +827,8 @@ main(int ac, char **av) + else if (strcmp(optarg, "kex") == 0 || + strcasecmp(optarg, "KexAlgorithms") == 0) + cp = kex_alg_list('\n'); ++ else if (strcmp(optarg, "kex-gss") == 0) ++ cp = kex_gss_alg_list('\n'); + else if (strcmp(optarg, "key") == 0) + cp = sshkey_alg_list(0, 0, 0, '\n'); + else if (strcmp(optarg, "key-cert") == 0) +@@ -857,8 +859,8 @@ main(int ac, char **av) + } else if (strcmp(optarg, "help") == 0) { + cp = xstrdup( + "cipher\ncipher-auth\ncompression\nkex\n" +- "key\nkey-cert\nkey-plain\nkey-sig\nmac\n" +- "protocol-version\nsig"); ++ "kex-gss\nkey\nkey-cert\nkey-plain\n" ++ "key-sig\nmac\nprotocol-version\nsig"); + } + if (cp == NULL) + fatal("Unsupported query \"%s\"", optarg); +diff --color -ruNp a/ssh_config b/ssh_config +--- a/ssh_config 2024-09-16 11:45:56.884133795 +0200 ++++ b/ssh_config 2024-09-16 11:46:34.702940054 +0200 +@@ -24,6 +24,8 @@ + # HostbasedAuthentication no + # GSSAPIAuthentication no + # GSSAPIDelegateCredentials no ++# GSSAPIKeyExchange no ++# GSSAPITrustDNS no + # BatchMode no + # CheckHostIP no + # AddressFamily any +diff --color -ruNp a/ssh_config.5 b/ssh_config.5 +--- a/ssh_config.5 2024-07-01 06:36:28.000000000 +0200 ++++ b/ssh_config.5 2024-09-16 11:46:34.703940075 +0200 +@@ -938,10 +938,68 @@ The default is + Specifies whether user authentication based on GSSAPI is allowed. + The default is + .Cm no . ++.It Cm GSSAPIClientIdentity ++If set, specifies the GSSAPI client identity that ssh should use when ++connecting to the server. The default is unset, which means that the default ++identity will be used. + .It Cm GSSAPIDelegateCredentials + Forward (delegate) credentials to the server. + The default is + .Cm no . ++.It Cm GSSAPIKeyExchange ++Specifies whether key exchange based on GSSAPI may be used. When using ++GSSAPI key exchange the server need not have a host key. ++The default is ++.Dq no . ++.It Cm GSSAPIRenewalForcesRekey ++If set to ++.Dq yes ++then renewal of the client's GSSAPI credentials will force the rekeying of the ++ssh connection. With a compatible server, this will delegate the renewed ++credentials to a session on the server. ++.Pp ++Checks are made to ensure that credentials are only propagated when the new ++credentials match the old ones on the originating client and where the ++receiving server still has the old set in its cache. ++.Pp ++The default is ++.Dq no . ++.Pp ++For this to work ++.Cm GSSAPIKeyExchange ++needs to be enabled in the server and also used by the client. ++.It Cm GSSAPIServerIdentity ++If set, specifies the GSSAPI server identity that ssh should expect when ++connecting to the server. The default is unset, which means that the ++expected GSSAPI server identity will be determined from the target ++hostname. ++.It Cm GSSAPITrustDns ++Set to ++.Dq yes ++to indicate that the DNS is trusted to securely canonicalize ++the name of the host being connected to. If ++.Dq no , ++the hostname entered on the ++command line will be passed untouched to the GSSAPI library. ++The default is ++.Dq no . ++.It Cm GSSAPIKexAlgorithms ++The list of key exchange algorithms that are offered for GSSAPI ++key exchange. Possible values are ++.Bd -literal -offset 3n ++gss-gex-sha1-, ++gss-group1-sha1-, ++gss-group14-sha1-, ++gss-group14-sha256-, ++gss-group16-sha512-, ++gss-nistp256-sha256-, ++gss-curve25519-sha256- ++.Ed ++.Pp ++The default is ++.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-, ++gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1- . ++This option only applies to connections using GSSAPI. + .It Cm HashKnownHosts + Indicates that + .Xr ssh 1 +diff --color -ruNp a/sshconnect2.c b/sshconnect2.c +--- a/sshconnect2.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/sshconnect2.c 2024-09-16 11:46:34.703940075 +0200 +@@ -222,6 +222,11 @@ ssh_kex2(struct ssh *ssh, char *host, st + char *all_key, *hkalgs = NULL; + int r, use_known_hosts_order = 0; + ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ char *orig = NULL, *gss = NULL; ++ char *gss_host = NULL; ++#endif ++ + xxx_host = host; + xxx_hostaddr = hostaddr; + xxx_conn_info = cinfo; +@@ -255,6 +260,42 @@ ssh_kex2(struct ssh *ssh, char *host, st + compression_alg_list(options.compression), + hkalgs ? hkalgs : options.hostkeyalgorithms); + ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ if (options.gss_keyex) { ++ /* Add the GSSAPI mechanisms currently supported on this ++ * client to the key exchange algorithm proposal */ ++ orig = myproposal[PROPOSAL_KEX_ALGS]; ++ ++ if (options.gss_server_identity) { ++ gss_host = xstrdup(options.gss_server_identity); ++ } else if (options.gss_trust_dns) { ++ gss_host = remote_hostname(ssh); ++ /* Fall back to specified host if we are using proxy command ++ * and can not use DNS on that socket */ ++ if (strcmp(gss_host, "UNKNOWN") == 0) { ++ free(gss_host); ++ gss_host = xstrdup(host); ++ } ++ } else { ++ gss_host = xstrdup(host); ++ } ++ ++ gss = ssh_gssapi_client_mechanisms(gss_host, ++ options.gss_client_identity, options.gss_kex_algorithms); ++ if (gss) { ++ debug("Offering GSSAPI proposal: %s", gss); ++ xasprintf(&myproposal[PROPOSAL_KEX_ALGS], ++ "%s,%s", gss, orig); ++ ++ /* If we've got GSSAPI algorithms, then we also support the ++ * 'null' hostkey, as a last resort */ ++ orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]; ++ xasprintf(&myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS], ++ "%s,null", orig); ++ } ++ } ++#endif ++ + free(hkalgs); + + /* start key exchange */ +@@ -271,15 +312,45 @@ ssh_kex2(struct ssh *ssh, char *host, st + # ifdef OPENSSL_HAS_ECC + ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client; + # endif +-#endif ++# ifdef GSSAPI ++ if (options.gss_keyex) { ++ ssh->kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client; ++ ssh->kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_client; ++ ssh->kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_client; ++ ssh->kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_client; ++ ssh->kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_client; ++ ssh->kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_client; ++ ssh->kex->kex[KEX_GSS_C25519_SHA256] = kexgss_client; ++ } ++# endif ++#endif /* WITH_OPENSSL */ + ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; + ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client; + ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client; + ssh->kex->verify_host_key=&verify_host_key_callback; + ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ if (options.gss_keyex) { ++ ssh->kex->gss_deleg_creds = options.gss_deleg_creds; ++ ssh->kex->gss_trust_dns = options.gss_trust_dns; ++ ssh->kex->gss_client = options.gss_client_identity; ++ ssh->kex->gss_host = gss_host; ++ } ++#endif ++ + ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &ssh->kex->done); + kex_proposal_free_entries(myproposal); + ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ /* repair myproposal after it was crumpled by the */ ++ /* ext-info removal above */ ++ if (gss) { ++ orig = myproposal[PROPOSAL_KEX_ALGS]; ++ xasprintf(&myproposal[PROPOSAL_KEX_ALGS], ++ "%s,%s", gss, orig); ++ free(gss); ++ } ++#endif + #ifdef DEBUG_KEXDH + /* send 1st encrypted/maced/compressed message */ + if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 || +@@ -368,6 +439,7 @@ static int input_gssapi_response(int typ + static int input_gssapi_token(int type, u_int32_t, struct ssh *); + static int input_gssapi_error(int, u_int32_t, struct ssh *); + static int input_gssapi_errtok(int, u_int32_t, struct ssh *); ++static int userauth_gsskeyex(struct ssh *); + #endif + + void userauth(struct ssh *, char *); +@@ -384,6 +456,11 @@ static char *authmethods_get(void); + + Authmethod authmethods[] = { + #ifdef GSSAPI ++ {"gssapi-keyex", ++ userauth_gsskeyex, ++ NULL, ++ &options.gss_keyex, ++ NULL}, + {"gssapi-with-mic", + userauth_gssapi, + userauth_gssapi_cleanup, +@@ -755,12 +832,32 @@ userauth_gssapi(struct ssh *ssh) + OM_uint32 min; + int r, ok = 0; + gss_OID mech = NULL; ++ char *gss_host = NULL; ++ ++ if (options.gss_server_identity) { ++ gss_host = xstrdup(options.gss_server_identity); ++ } else if (options.gss_trust_dns) { ++ gss_host = remote_hostname(ssh); ++ /* Fall back to specified host if we are using proxy command ++ * and can not use DNS on that socket */ ++ if (strcmp(gss_host, "UNKNOWN") == 0) { ++ free(gss_host); ++ gss_host = xstrdup(authctxt->host); ++ } ++ } else { ++ gss_host = xstrdup(authctxt->host); ++ } + + /* Try one GSSAPI method at a time, rather than sending them all at + * once. */ + + if (authctxt->gss_supported_mechs == NULL) +- gss_indicate_mechs(&min, &authctxt->gss_supported_mechs); ++ if (GSS_ERROR(gss_indicate_mechs(&min, ++ &authctxt->gss_supported_mechs))) { ++ authctxt->gss_supported_mechs = NULL; ++ free(gss_host); ++ return 0; ++ } + + /* Check to see whether the mechanism is usable before we offer it */ + while (authctxt->mech_tried < authctxt->gss_supported_mechs->count && +@@ -769,13 +866,15 @@ userauth_gssapi(struct ssh *ssh) + elements[authctxt->mech_tried]; + /* My DER encoding requires length<128 */ + if (mech->length < 128 && ssh_gssapi_check_mechanism(&gssctxt, +- mech, authctxt->host)) { ++ mech, gss_host, options.gss_client_identity)) { + ok = 1; /* Mechanism works */ + } else { + authctxt->mech_tried++; + } + } + ++ free(gss_host); ++ + if (!ok || mech == NULL) + return 0; + +@@ -1009,6 +1108,55 @@ input_gssapi_error(int type, u_int32_t p + free(lang); + return r; + } ++ ++int ++userauth_gsskeyex(struct ssh *ssh) ++{ ++ struct sshbuf *b = NULL; ++ Authctxt *authctxt = ssh->authctxt; ++ gss_buffer_desc gssbuf; ++ gss_buffer_desc mic = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ms; ++ int r; ++ ++ static int attempt = 0; ++ if (attempt++ >= 1) ++ return (0); ++ ++ if (gss_kex_context == NULL) { ++ debug("No valid Key exchange context"); ++ return (0); ++ } ++ ++ if ((b = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new failed"); ++ ++ ssh_gssapi_buildmic(b, authctxt->server_user, authctxt->service, ++ "gssapi-keyex", ssh->kex->session_id); ++ ++ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL) ++ fatal_f("sshbuf_mutable_ptr failed"); ++ gssbuf.length = sshbuf_len(b); ++ ++ if (GSS_ERROR(ssh_gssapi_sign(gss_kex_context, &gssbuf, &mic))) { ++ sshbuf_free(b); ++ return (0); ++ } ++ ++ if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_REQUEST)) != 0 || ++ (r = sshpkt_put_cstring(ssh, authctxt->server_user)) != 0 || ++ (r = sshpkt_put_cstring(ssh, authctxt->service)) != 0 || ++ (r = sshpkt_put_cstring(ssh, authctxt->method->name)) != 0 || ++ (r = sshpkt_put_string(ssh, mic.value, mic.length)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal_fr(r, "parsing"); ++ ++ sshbuf_free(b); ++ gss_release_buffer(&ms, &mic); ++ ++ return (1); ++} ++ + #endif /* GSSAPI */ + + static int +diff --color -ruNp a/sshd.c b/sshd.c +--- a/sshd.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/sshd.c 2024-09-16 11:46:34.704940096 +0200 +@@ -1551,7 +1551,8 @@ main(int ac, char **av) + free(fp); + } + accumulate_host_timing_secret(cfg, NULL); +- if (!sensitive_data.have_ssh2_key) { ++ /* The GSSAPI key exchange can run without a host key */ ++ if (!sensitive_data.have_ssh2_key && !options.gss_keyex) { + logit("sshd: no hostkeys available -- exiting."); + exit(1); + } +diff --color -ruNp a/sshd_config b/sshd_config +--- a/sshd_config 2024-09-16 11:45:56.888133880 +0200 ++++ b/sshd_config 2024-09-16 11:46:34.704940096 +0200 +@@ -77,6 +77,8 @@ AuthorizedKeysFile .ssh/authorized_keys + # GSSAPI options + #GSSAPIAuthentication no + #GSSAPICleanupCredentials yes ++#GSSAPIStrictAcceptorCheck yes ++#GSSAPIKeyExchange no + + # Set this to 'yes' to enable PAM authentication, account processing, + # and session processing. If this is enabled, PAM authentication will +diff --color -ruNp a/sshd_config.5 b/sshd_config.5 +--- a/sshd_config.5 2024-09-16 11:45:56.885133816 +0200 ++++ b/sshd_config.5 2024-09-16 11:46:34.704940096 +0200 +@@ -739,6 +739,11 @@ Specifies whether to automatically destr + on logout. + The default is + .Cm yes . ++.It Cm GSSAPIKeyExchange ++Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange ++doesn't rely on ssh keys to verify host identity. ++The default is ++.Cm no . + .It Cm GSSAPIStrictAcceptorCheck + Determines whether to be strict about the identity of the GSSAPI acceptor + a client authenticates against. +@@ -753,6 +758,32 @@ machine's default store. + This facility is provided to assist with operation on multi homed machines. + The default is + .Cm yes . ++.It Cm GSSAPIStoreCredentialsOnRekey ++Controls whether the user's GSSAPI credentials should be updated following a ++successful connection rekeying. This option can be used to accepted renewed ++or updated credentials from a compatible client. The default is ++.Dq no . ++.Pp ++For this to work ++.Cm GSSAPIKeyExchange ++needs to be enabled in the server and also used by the client. ++.It Cm GSSAPIKexAlgorithms ++The list of key exchange algorithms that are accepted by GSSAPI ++key exchange. Possible values are ++.Bd -literal -offset 3n ++gss-gex-sha1-, ++gss-group1-sha1-, ++gss-group14-sha1-, ++gss-group14-sha256-, ++gss-group16-sha512-, ++gss-nistp256-sha256-, ++gss-curve25519-sha256- ++.Ed ++.Pp ++The default is ++.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-, ++gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1- . ++This option only applies to connections using GSSAPI. + .It Cm HostbasedAcceptedAlgorithms + Specifies the signature algorithms that will be accepted for hostbased + authentication as a list of comma-separated patterns. +diff --color -ruNp a/sshd-session.c b/sshd-session.c +--- a/sshd-session.c 2024-09-16 11:45:56.888133880 +0200 ++++ b/sshd-session.c 2024-09-16 11:46:34.705940118 +0200 +@@ -660,8 +660,8 @@ notify_hostkeys(struct ssh *ssh) + } + debug3_f("sent %u hostkeys", nkeys); + if (nkeys == 0) +- fatal_f("no hostkeys"); +- if ((r = sshpkt_send(ssh)) != 0) ++ debug3_f("no hostkeys"); ++ else if ((r = sshpkt_send(ssh)) != 0) + sshpkt_fatal(ssh, r, "%s: send", __func__); + sshbuf_free(buf); + } +@@ -1180,8 +1180,9 @@ main(int ac, char **av) + break; + } + } +- if (!have_key) +- fatal("internal error: monitor received no hostkeys"); ++ /* The GSSAPI key exchange can run without a host key */ ++ if (!have_key && !options.gss_keyex) ++ fatal("internal error: monitor received no hostkeys and GSS KEX is not configured"); + + /* Ensure that umask disallows at least group and world write */ + new_umask = umask(0077) | 0022; +@@ -1462,6 +1463,48 @@ do_ssh2_kex(struct ssh *ssh) + + free(hkalgs); + ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ { ++ char *orig; ++ char *gss = NULL; ++ char *newstr = NULL; ++ orig = myproposal[PROPOSAL_KEX_ALGS]; ++ ++ /* ++ * If we don't have a host key, then there's no point advertising ++ * the other key exchange algorithms ++ */ ++ ++ if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]) == 0) ++ orig = NULL; ++ ++ if (options.gss_keyex) ++ gss = ssh_gssapi_server_mechanisms(); ++ else ++ gss = NULL; ++ ++ if (gss && orig) ++ xasprintf(&newstr, "%s,%s", gss, orig); ++ else if (gss) ++ xasprintf(&newstr, "%s,%s", gss, "kex-strict-s-v00@openssh.com"); ++ else if (orig) ++ newstr = orig; ++ ++ /* ++ * If we've got GSSAPI mechanisms, then we've got the 'null' host ++ * key alg, but we can't tell people about it unless its the only ++ * host key algorithm we support ++ */ ++ if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0) ++ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = xstrdup("null"); ++ ++ if (newstr) ++ myproposal[PROPOSAL_KEX_ALGS] = newstr; ++ else ++ fatal("No supported key exchange algorithms"); ++ } ++#endif ++ + /* start key exchange */ + if ((r = kex_setup(ssh, myproposal)) != 0) + fatal_r(r, "kex_setup"); +@@ -1479,7 +1522,18 @@ do_ssh2_kex(struct ssh *ssh) + #ifdef OPENSSL_HAS_ECC + kex->kex[KEX_ECDH_SHA2] = kex_gen_server; + #endif +-#endif ++# ifdef GSSAPI ++ if (options.gss_keyex) { ++ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; ++ kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; ++ kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server; ++ kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server; ++ kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server; ++ kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server; ++ kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server; ++ } ++# endif ++#endif /* WITH_OPENSSL */ + kex->kex[KEX_C25519_SHA256] = kex_gen_server; + kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; + kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server; +diff --color -ruNp a/ssh-gss.h b/ssh-gss.h +--- a/ssh-gss.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/ssh-gss.h 2024-09-16 11:46:34.710940224 +0200 +@@ -61,10 +61,36 @@ + + #define SSH_GSS_OIDTYPE 0x06 + ++#define SSH2_MSG_KEXGSS_INIT 30 ++#define SSH2_MSG_KEXGSS_CONTINUE 31 ++#define SSH2_MSG_KEXGSS_COMPLETE 32 ++#define SSH2_MSG_KEXGSS_HOSTKEY 33 ++#define SSH2_MSG_KEXGSS_ERROR 34 ++#define SSH2_MSG_KEXGSS_GROUPREQ 40 ++#define SSH2_MSG_KEXGSS_GROUP 41 ++#define KEX_GSS_GRP1_SHA1_ID "gss-group1-sha1-" ++#define KEX_GSS_GRP14_SHA1_ID "gss-group14-sha1-" ++#define KEX_GSS_GRP14_SHA256_ID "gss-group14-sha256-" ++#define KEX_GSS_GRP16_SHA512_ID "gss-group16-sha512-" ++#define KEX_GSS_GEX_SHA1_ID "gss-gex-sha1-" ++#define KEX_GSS_NISTP256_SHA256_ID "gss-nistp256-sha256-" ++#define KEX_GSS_C25519_SHA256_ID "gss-curve25519-sha256-" ++ ++#define GSS_KEX_DEFAULT_KEX \ ++ KEX_GSS_GRP14_SHA256_ID "," \ ++ KEX_GSS_GRP16_SHA512_ID "," \ ++ KEX_GSS_NISTP256_SHA256_ID "," \ ++ KEX_GSS_C25519_SHA256_ID "," \ ++ KEX_GSS_GRP14_SHA1_ID "," \ ++ KEX_GSS_GEX_SHA1_ID ++ ++#include "digest.h" /* SSH_DIGEST_MAX_LENGTH */ ++ + typedef struct { + char *filename; + char *envvar; + char *envval; ++ struct passwd *owner; + void *data; + } ssh_gssapi_ccache; + +@@ -72,8 +98,11 @@ typedef struct { + gss_buffer_desc displayname; + gss_buffer_desc exportedname; + gss_cred_id_t creds; ++ gss_name_t name; + struct ssh_gssapi_mech_struct *mech; + ssh_gssapi_ccache store; ++ int used; ++ int updated; + } ssh_gssapi_client; + + typedef struct ssh_gssapi_mech_struct { +@@ -84,6 +113,7 @@ typedef struct ssh_gssapi_mech_struct { + int (*userok) (ssh_gssapi_client *, char *); + int (*localname) (ssh_gssapi_client *, char **); + void (*storecreds) (ssh_gssapi_client *); ++ int (*updatecreds) (ssh_gssapi_ccache *, ssh_gssapi_client *); + } ssh_gssapi_mech; + + typedef struct { +@@ -94,10 +124,21 @@ typedef struct { + gss_OID oid; /* client */ + gss_cred_id_t creds; /* server */ + gss_name_t client; /* server */ +- gss_cred_id_t client_creds; /* server */ ++ gss_cred_id_t client_creds; /* both */ ++ struct sshbuf *shared_secret; /* both */ ++ struct sshbuf *server_pubkey; /* server */ ++ struct sshbuf *server_blob; /* client */ ++ struct sshbuf *server_host_key_blob; /* client */ ++ gss_buffer_desc msg_tok; /* client */ ++ gss_buffer_desc buf; /* both */ ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; /* both */ ++ size_t hashlen; /* both */ ++ int first; /* client */ ++ BIGNUM *dh_client_pub; /* server (gex) */ + } Gssctxt; + + extern ssh_gssapi_mech *supported_mechs[]; ++extern Gssctxt *gss_kex_context; + + int ssh_gssapi_check_oid(Gssctxt *, void *, size_t); + void ssh_gssapi_set_oid_data(Gssctxt *, void *, size_t); +@@ -108,6 +149,7 @@ OM_uint32 ssh_gssapi_test_oid_supported( + + struct sshbuf; + int ssh_gssapi_get_buffer_desc(struct sshbuf *, gss_buffer_desc *); ++int ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *, gss_buffer_desc *); + + OM_uint32 ssh_gssapi_import_name(Gssctxt *, const char *); + OM_uint32 ssh_gssapi_init_ctx(Gssctxt *, int, +@@ -122,17 +164,33 @@ void ssh_gssapi_delete_ctx(Gssctxt **); + OM_uint32 ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); + void ssh_gssapi_buildmic(struct sshbuf *, const char *, + const char *, const char *, const struct sshbuf *); +-int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *); ++int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *, const char *); ++OM_uint32 ssh_gssapi_client_identity(Gssctxt *, const char *); ++int ssh_gssapi_credentials_updated(Gssctxt *); + + /* In the server */ ++typedef int ssh_gssapi_check_fn(Gssctxt **, gss_OID, const char *, ++ const char *); ++char *ssh_gssapi_client_mechanisms(const char *, const char *, const char *); ++char *ssh_gssapi_kex_mechs(gss_OID_set, ssh_gssapi_check_fn *, const char *, ++ const char *, const char *); ++gss_OID ssh_gssapi_id_kex(Gssctxt *, char *, int); ++int ssh_gssapi_server_check_mech(Gssctxt **,gss_OID, const char *, ++ const char *); + OM_uint32 ssh_gssapi_server_ctx(Gssctxt **, gss_OID); +-int ssh_gssapi_userok(char *name); ++int ssh_gssapi_userok(char *name, struct passwd *, int kex); + OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); + void ssh_gssapi_do_child(char ***, u_int *); + void ssh_gssapi_cleanup_creds(void); + void ssh_gssapi_storecreds(void); + const char *ssh_gssapi_displayname(void); + ++char *ssh_gssapi_server_mechanisms(void); ++int ssh_gssapi_oid_table_ok(void); ++ ++int ssh_gssapi_update_creds(ssh_gssapi_ccache *store); ++void ssh_gssapi_rekey_creds(void); ++ + #endif /* GSSAPI */ + + #endif /* _SSH_GSS_H */ +diff --color -ruNp a/sshkey.c b/sshkey.c +--- a/sshkey.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/sshkey.c 2024-09-16 11:46:34.706940139 +0200 +@@ -131,6 +131,75 @@ extern const struct sshkey_impl sshkey_x + extern const struct sshkey_impl sshkey_xmss_cert_impl; + #endif + ++static int ssh_gss_equal(const struct sshkey *, const struct sshkey *) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_serialize_public(const struct sshkey *, struct sshbuf *, ++ enum sshkey_serialize_rep) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_deserialize_public(const char *, struct sshbuf *, ++ struct sshkey *) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_serialize_private(const struct sshkey *, struct sshbuf *, ++ enum sshkey_serialize_rep) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_deserialize_private(const char *, struct sshbuf *, ++ struct sshkey *) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_copy_public(const struct sshkey *, struct sshkey *) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_verify(const struct sshkey *, const u_char *, size_t, ++ const u_char *, size_t, const char *, u_int, ++ struct sshkey_sig_details **) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static const struct sshkey_impl_funcs sshkey_gss_funcs = { ++ /* .size = */ NULL, ++ /* .alloc = */ NULL, ++ /* .cleanup = */ NULL, ++ /* .equal = */ ssh_gss_equal, ++ /* .ssh_serialize_public = */ ssh_gss_serialize_public, ++ /* .ssh_deserialize_public = */ ssh_gss_deserialize_public, ++ /* .ssh_serialize_private = */ ssh_gss_serialize_private, ++ /* .ssh_deserialize_private = */ ssh_gss_deserialize_private, ++ /* .generate = */ NULL, ++ /* .copy_public = */ ssh_gss_copy_public, ++ /* .sign = */ NULL, ++ /* .verify = */ ssh_gss_verify, ++}; ++ ++/* The struct is intentionally dummy and has no gss calls */ ++static const struct sshkey_impl sshkey_gss_kex_impl = { ++ /* .name = */ "null", ++ /* .shortname = */ "null", ++ /* .sigalg = */ NULL, ++ /* .type = */ KEY_NULL, ++ /* .nid = */ 0, ++ /* .cert = */ 0, ++ /* .sigonly = */ 0, ++ /* .keybits = */ 0, /* FIXME */ ++ /* .funcs = */ &sshkey_gss_funcs, ++}; ++ + const struct sshkey_impl * const keyimpls[] = { + &sshkey_ed25519_impl, + &sshkey_ed25519_cert_impl, +@@ -169,6 +238,7 @@ const struct sshkey_impl * const keyimpl + &sshkey_xmss_impl, + &sshkey_xmss_cert_impl, + #endif ++ &sshkey_gss_kex_impl, + NULL + }; + +@@ -324,7 +394,7 @@ sshkey_alg_list(int certs_only, int plai + + for (i = 0; keyimpls[i] != NULL; i++) { + impl = keyimpls[i]; +- if (impl->name == NULL) ++ if (impl->name == NULL || impl->type == KEY_NULL) + continue; + if (!include_sigonly && impl->sigonly) + continue; +diff --color -ruNp a/sshkey.h b/sshkey.h +--- a/sshkey.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/sshkey.h 2024-09-16 11:46:34.706940139 +0200 +@@ -71,6 +71,7 @@ enum sshkey_types { + KEY_ECDSA_SK_CERT, + KEY_ED25519_SK, + KEY_ED25519_SK_CERT, ++ KEY_NULL, + KEY_UNSPEC + }; + diff --git a/openssh-9.6p1-pam-rhost.patch b/openssh-9.6p1-pam-rhost.patch new file mode 100644 index 0000000000000000000000000000000000000000..b1b0d0440dfc181d189cb68c3712aa06cfa10867 --- /dev/null +++ b/openssh-9.6p1-pam-rhost.patch @@ -0,0 +1,32 @@ +From 26f366e263e575c4e1a18e2e64ba418f58878b37 Mon Sep 17 00:00:00 2001 +From: Daan De Meyer +Date: Mon, 20 Mar 2023 20:22:14 +0100 +Subject: [PATCH] Only set PAM_RHOST if the remote host is not "UNKNOWN" + +When using sshd's -i option with stdio that is not a AF_INET/AF_INET6 +socket, auth_get_canonical_hostname() returns "UNKNOWN" which is then +set as the value of PAM_RHOST, causing pam to try to do a reverse DNS +query of "UNKNOWN", which times out multiple times, causing a +substantial slowdown when logging in. + +To fix this, let's only set PAM_RHOST if the hostname is not "UNKNOWN". +--- + auth-pam.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/auth-pam.c b/auth-pam.c +index e143304e3..39b4e4563 100644 +--- a/auth-pam.c ++++ b/auth-pam.c +@@ -735,7 +735,7 @@ sshpam_init(struct ssh *ssh, Authctxt *authctxt) + sshpam_laddr = get_local_ipaddr( + ssh_packet_get_connection_in(ssh)); + } +- if (sshpam_rhost != NULL) { ++ if (sshpam_rhost != NULL && strcmp(sshpam_rhost, "UNKNOWN") != 0) { + debug("PAM: setting PAM_RHOST to \"%s\"", sshpam_rhost); + sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, + sshpam_rhost); +-- +2.44.0 + diff --git a/openssh-9.9p1-mlkembe.patch b/openssh-9.9p1-mlkembe.patch new file mode 100644 index 0000000000000000000000000000000000000000..aa0c26cd31987f5991245dd06c10dca4c69206fd --- /dev/null +++ b/openssh-9.9p1-mlkembe.patch @@ -0,0 +1,98 @@ +diff --git a/kexmlkem768x25519.c b/kexmlkem768x25519.c +index 679446e9..2b5d3960 100644 +--- a/kexmlkem768x25519.c ++++ b/kexmlkem768x25519.c +@@ -1,4 +1,4 @@ +-/* $OpenBSD: kexmlkem768x25519.c,v 1.1 2024/09/02 12:13:56 djm Exp $ */ ++/* $OpenBSD: kexmlkem768x25519.c,v 1.2 2024/10/27 02:06:59 djm Exp $ */ + /* + * Copyright (c) 2023 Markus Friedl. All rights reserved. + * +@@ -34,6 +34,9 @@ + #include + #include + #include ++#ifdef HAVE_ENDIAN_H ++# include ++#endif + + #include "sshkey.h" + #include "kex.h" +diff --git a/libcrux_mlkem768_sha3.h b/libcrux_mlkem768_sha3.h +index a82d60e8..b8ac1436 100644 +--- a/libcrux_mlkem768_sha3.h ++++ b/libcrux_mlkem768_sha3.h +@@ -1,4 +1,5 @@ +-/* $OpenBSD: libcrux_mlkem768_sha3.h,v 1.1 2024/09/02 12:13:56 djm Exp $ */ ++/* $OpenBSD: libcrux_mlkem768_sha3.h,v 1.2 2024/10/27 02:06:01 djm Exp $ */ ++ + /* Extracted from libcrux revision 84c5d87b3092c59294345aa269ceefe0eb97cc35 */ + + /* +@@ -160,18 +161,19 @@ static inline void Eurydice_slice_to_array3(uint8_t *dst_tag, char *dst_ok, + // CORE STUFF (conversions, endianness, ...) + + static inline void core_num__u64_9__to_le_bytes(uint64_t v, uint8_t buf[8]) { ++ v = htole64(v); + memcpy(buf, &v, sizeof(v)); + } + static inline uint64_t core_num__u64_9__from_le_bytes(uint8_t buf[8]) { + uint64_t v; + memcpy(&v, buf, sizeof(v)); +- return v; ++ return le64toh(v); + } + + static inline uint32_t core_num__u32_8__from_le_bytes(uint8_t buf[4]) { + uint32_t v; + memcpy(&v, buf, sizeof(v)); +- return v; ++ return le32toh(v); + } + + static inline uint32_t core_num__u8_6__count_ones(uint8_t x0) { +diff --git a/mlkem768.sh b/mlkem768.sh +index 2fdc2831..3d12b2ed 100644 +--- a/mlkem768.sh ++++ b/mlkem768.sh +@@ -1,9 +1,10 @@ + #!/bin/sh +-# $OpenBSD: mlkem768.sh,v 1.2 2024/09/04 05:11:33 djm Exp $ ++# $OpenBSD: mlkem768.sh,v 1.3 2024/10/27 02:06:01 djm Exp $ + # Placed in the Public Domain. + # + +-WANT_LIBCRUX_REVISION="origin/main" ++#WANT_LIBCRUX_REVISION="origin/main" ++WANT_LIBCRUX_REVISION="84c5d87b3092c59294345aa269ceefe0eb97cc35" + + FILES=" + libcrux/libcrux-ml-kem/cg/eurydice_glue.h +@@ -47,6 +48,7 @@ echo '#define KRML_NOINLINE __attribute__((noinline, unused))' + echo '#define KRML_HOST_EPRINTF(...)' + echo '#define KRML_HOST_EXIT(x) fatal_f("internal error")' + echo ++ + for i in $FILES; do + echo "/* from $i */" + # Changes to all files: +@@ -56,11 +58,16 @@ for i in $FILES; do + -e 's/[ ]*$//' \ + $i | \ + case "$i" in +- # XXX per-file handling goes here. ++ */libcrux-ml-kem/cg/eurydice_glue.h) ++ # Replace endian functions with versions that work. ++ perl -0777 -pe 's/(static inline void core_num__u64_9__to_le_bytes.*\n)([^}]*\n)/\1 v = htole64(v);\n\2/' | ++ perl -0777 -pe 's/(static inline uint64_t core_num__u64_9__from_le_bytes.*?)return v;/\1return le64toh(v);/s' | ++ perl -0777 -pe 's/(static inline uint32_t core_num__u32_8__from_le_bytes.*?)return v;/\1return le32toh(v);/s' ++ ;; + # Default: pass through. + *) +- cat +- ;; ++ cat ++ ;; + esac + echo + done diff --git a/openssh-9.9p1-separate-keysign.patch b/openssh-9.9p1-separate-keysign.patch new file mode 100644 index 0000000000000000000000000000000000000000..ff0e35fd2ea38da7779ea628c7785b6a770e6fc8 --- /dev/null +++ b/openssh-9.9p1-separate-keysign.patch @@ -0,0 +1,12 @@ +diff -up openssh-9.9p1/ssh_config.5.xxx openssh-9.9p1/ssh_config.5 +--- openssh-9.9p1/ssh_config.5.xxx 2024-10-11 12:01:14.260566303 +0200 ++++ openssh-9.9p1/ssh_config.5 2024-10-11 12:01:59.725654775 +0200 +@@ -759,7 +759,7 @@ or + This option should be placed in the non-hostspecific section. + See + .Xr ssh-keysign 8 +-for more information. ++for more information. ssh-keysign should be installed explicitly. + .It Cm EscapeChar + Sets the escape character (default: + .Ql ~ ) . diff --git a/openssh-9.9p1.tar.gz b/openssh-9.9p1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..ca34ba1f82119a76b3618f95630f64c5004ae78a Binary files /dev/null and b/openssh-9.9p1.tar.gz differ diff --git a/openssh-9.9p1.tar.gz.asc b/openssh-9.9p1.tar.gz.asc new file mode 100644 index 0000000000000000000000000000000000000000..9937cba6442b784d1930da52e2858405550fd1c7 --- /dev/null +++ b/openssh-9.9p1.tar.gz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCgAdFiEEcWi5g4FaXu9ZpK39Kj9BTnNgYLoFAmbspccACgkQKj9BTnNg +YLppxRAAv7eU/Xd2w9MX9vWQdhugiPByEcKg7KuKXUUs9xJGy+HbLqPqUCvn1UW6 +qodKoSAdeBuSB7AjzuIQ1lTVX7C67OmZaVPRq25ar5b+Wq4SSlv23KMRq0b4EVyw +pOW6R9tsxqYBwYaiXQ50APcYL8SpepnGU+b/iR15f7q3SU2XMVVtkVb149UdLOqK +smfurbDGwUKFb2Q009MUfEV/d9zq31tdSjphvkqAXCcmxc8siuOYWYcByuysie+m +NpaOpee0047L5JIxNSLsa2yZrJZhClP8LbTCH1Vfwr7l0KE5nvL2qAtPKI2XxGQC +3jXrDLzp10RFxV8sCym+QlY9pZyzGj9d3G7vCHtxWGQ1Y0Qt+xs18OeBpjiehRhl +WM3Y+cjoN35jBaGhOoHdh3ePZQdTUyZ16aSv0h/cUHOohiM7i/4XW+dQtkqsJsw4 +a81O0E64WrL8ho3Ju9mwcVZ9A0aEaftJsmJPDB+qYBjF/i7xcnH32LginzP5pel7 +/W0aS2C1ZNo3QKHezI6IA9MyENMZiAMy2ybvfmN0HgLBaBY1plJ8a5GvMwJc+Qwh +iCHLCQ6Qgf/1hh+F6liTXnhtedtFHneJdyqvd7XOoardDEipZjxcnGa4HthbDFU+ +8XdHKnWWhn4BLA+y7KB3ZGURniQK+qibwkF6J63CuMU+LmG+bvQ= +=Ukrb +-----END PGP SIGNATURE----- diff --git a/openssh.spec b/openssh.spec index 366b767d137e387079a4ff15e5754976b84907f0..aeb5b90e25a3569e031f81a95f2431174af03878 100644 --- a/openssh.spec +++ b/openssh.spec @@ -1,4 +1,4 @@ -%define anolis_release 6 +%define anolis_release 7 %global WITH_SELINUX 1 @@ -234,6 +234,25 @@ Patch1020: fix-CVE-2023-48795.patch Patch1021: fix-CVE-2023-51384.patch Patch1022: fix-CVE-2023-51385.patch +Patch1023: openssh-8.7p1-openssl-log.patch +Patch1024: fb87_080_logging_certificates.patch +Patch1025: openssh-8.7p1-redhat-help.patch +Patch1026: openssh-9.9p1-mlkembe.patch +Patch1027: fb87_sk_ecdsa_webauthn.patch +Patch1028: fb87_090_logging_shell_cmd_pty.patch +Patch1029: fb87_log_accept_env.patch +Patch1030: fb87_log_session_id.patch +Patch1031: fb87_pass_principals_to_child.patch +Patch1032: fb87_slog.patch +Patch1033: fb87_set_sock_tos.patch +Patch1034: fb87_810_increase_ssh_cert_max_principals.patch +Patch1035: fb87_070_logging_reverse_port_forward.patch +Patch1036: openssh-9.9p1-separate-keysign.patch +Patch1037: openssh-9.0p1-evp-fips-kex.patch +Patch1038: fb87_log_port_forwards.patch +Patch1039: openssh-9.6p1-pam-rhost.patch +Patch1040: fb87_log_auth_info.patch +Patch1041: openssh-9.6p1-gssapi-keyex.patch License: BSD-3-Clause AND BSD-2-Clause AND ISC AND SSH-OpenSSH AND ssh-keyscan AND sprintf AND LicenseRef-Fedora-Public-Domain AND X11-distribute-modifications-variant Requires: /sbin/nologin @@ -766,6 +785,9 @@ test -f %{sysconfig_anaconda} && \ %endif %changelog +* Wed May 28 2025 RELAI - 9.9p1-7 +- Apply patch to prevent unnecessary DNS lookups and improve login performance when remote host is UNKNOWN. + * Fri May 23 2025 ali - 9.3p2-6 - fix CVE-2023-51385 @@ -829,4 +851,4 @@ test -f %{sysconfig_anaconda} && \ - add openssh-server and openssh-clients as Requires for openssh-askpass to make sure update * Fri Mar 11 2022 zhang xianting - 8.8p1-1 -- Initial build for Anolis v23 +- Initial build for Anolis v23 \ No newline at end of file diff --git a/parallel_test.Makefile b/parallel_test.Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f49df302ab143f4e903dcd097d08d1c59e4c6928 --- /dev/null +++ b/parallel_test.Makefile @@ -0,0 +1,14 @@ +# just a Makefile parallel_test.sh uses to run stuff in parallel with make +%: + $(MAKE) -j1 -C .t/$* $* + +t-exec-%: + $(MAKE) -j1 -C ".t/t-exec-$*" \ + TEST_SSH_PORT=10$*0 \ + SKIP_LTESTS="$(shell cat .ltests/not-in/$*)" \ + BUILDDIR="$(shell pwd)/.t/t-exec-$*" \ + TEST_SHELL=sh \ + MAKE=make \ + TEST_SSH_TRACE=yes \ + TEST_SSH_FAIL_FATAL=yes \ + t-exec \ diff --git a/parallel_test.sh b/parallel_test.sh new file mode 100755 index 0000000000000000000000000000000000000000..c9c269d66c8d7ced4bfc7ed1106c59a978645c8c --- /dev/null +++ b/parallel_test.sh @@ -0,0 +1,91 @@ +#!/usr/bin/bash +set -uexo pipefail + +# The custom %check script to run the OpenSSH upstream testsuite in parallel. +# +# The upstream testsuite is serial, +# so the idea here is to split the testsuite into several $PARTS: +# * file-tests +# * interop-tests +# * unit +# * ltests-00 +# * ltests-01 +# * ... +# * ltests-23 +# and run them in parallel, using make, each in its own build subtree. + +PARALLEL_MAKEFILE=$1 + +SPLIT=24 +PARTS='file-tests interop-tests unit ' +for ((i = 1; i < SPLIT; i++)); do ii=$(printf %02d $i); + PARTS+="t-exec-$ii " +done + +# work around a selinux restriction: +#chcon -t unconfined_exec_t ssh-sk-helper || : + +# work around something else that only crops up in brew +export TEST_SSH_UNSAFE_PERMISSIONS=1 + +# create a .test directory to store all our files in: +mkdir -p .t .ltests/{in,not-in} + +# patch testsuite: use different ports to avoid port collisions +grep -REi 'port=[2-9][0-9]*' regress +sed -i 's|PORT=4242|PORT=$(expr $TEST_SSH_PORT + 1)|' \ + regress/test-exec.sh* +sed -i 's|^P=3301 # test port|P=$(expr $TEST_SSH_PORT + 1)|' \ + regress/multiplex.sh* +sed -i 's|^fwdport=3301|fwdport=$(expr $TEST_SSH_PORT + 1)|' \ + regress/cfgmatch.sh* regress/cfgmatchlisten.sh* +sed -i 's|^LFWD_PORT=.*|LFWD_PORT=$(expr $TEST_SSH_PORT + 1)|' \ + regress/forward-control.sh* +sed -i 's|^RFWD_PORT=.*|RFWD_PORT=$(expr $TEST_SSH_PORT + 2)|' \ + regress/forward-control.sh* +( ! grep -REi 'port=[2-9][0-9]*' regress) # try to find more of those + +# patch testsuite: speed up +sed -i 's|sleep 1$|sleep .25|' regress/forward-control.sh + +# extract LTESTS list to .tests/ltests/all: +grep -Ex 'tests:[[:space:]]*file-tests t-exec interop-tests extra-tests unit' Makefile +echo -ne '\necho-ltests:\n\techo ${LTESTS}' >> regress/Makefile +make -s -C regress echo-ltests | tr ' ' '\n' > .ltests/all + +# separate ltests into $SPLIT roughly equal .tests/ltests/in/$ii parts: +grep -qFx connect .ltests/all +( ! grep -qFx nonex .ltests/all ) +split -d -a2 --number=l/$SPLIT .ltests/all .ltests/in/ +wc -l .ltests/in/* +grep -qFx connect .ltests/in/* + +# generate the inverses of them --- .ltests/not-in/$ii: +( ! grep -qFx nonex .ltests/in/* ) +for ((i = 0; i < SPLIT; i++)); do ii=$(printf %02d $i); + while read -r tname; do + if ! grep -qFx "$tname" ".ltests/in/$ii"; then + echo -n "$tname " >> ".ltests/not-in/$ii" + fi + done < .ltests/all +done +grep . .ltests/not-in/* +( ! grep -q ^connect .ltests/not-in/0 ) +for ((i = 1; i < SPLIT; i++)); do ii=$(printf %02d $i); + grep -q ^connect .ltests/not-in/$ii +done + +# prepare several test directories: +for PART in $PARTS; do + mkdir .t/${PART} + cp -ra * .t/${PART}/ + sed -i "s|abs_top_srcdir=.*|abs_top_srcdir=$(pwd)/.t/${PART}|" \ + .t/${PART}/Makefile + sed -i "s|abs_top_builddir=.*|abs_top_builddir=$(pwd)/.t/${PART}|" \ + .t/${PART}/Makefile + sed -i "s|^BUILDDIR=.*|BUILDDIR=$(pwd)/.t/${PART}|" \ + .t/${PART}/Makefile +done + +# finally, run tests $PARTS in parallel in their own subtrees: +time make -f "$PARALLEL_MAKEFILE" -j$(nproc) $PARTS diff --git a/series b/series new file mode 100644 index 0000000000000000000000000000000000000000..9671c48fe643b123724f4e23c8c238e962da758f --- /dev/null +++ b/series @@ -0,0 +1,13 @@ +fb87_log_session_id.patch +fb87_slog.patch +fb87_log_port_forwards.patch +fb87_070_logging_reverse_port_forward.patch +fb87_810_increase_ssh_cert_max_principals.patch +fb87_090_logging_shell_cmd_pty.patch +fb87_080_logging_certificates.patch +fb87_log_accept_env.patch +fb87_pass_principals_to_child.patch +fb87_log_auth_info.patch +fb87_sk_ecdsa_webauthn.patch +fb87_set_sock_tos.patch + diff --git a/sshd-keygen b/sshd-keygen index 170ada07a9d0f48a49dd00405fb8509ef1b5678b..b15fc8cc16b2781401ecf7948d80bd8140ca827d 100644 --- a/sshd-keygen +++ b/sshd-keygen @@ -9,8 +9,14 @@ case $KEYTYPE in if [[ -r "$FIPS" && $(cat $FIPS) == "1" ]]; then exit 0 fi ;; - "rsa") ;; # always ok - "ecdsa") ;; + "rsa") + if [[ ! -z $SSH_RSA_BITS ]]; then + SSH_KEYGEN_OPTIONS="-b $SSH_RSA_BITS" + fi ;; # always ok + "ecdsa") + if [[ ! -z $SSH_ECDSA_BITS ]]; then + SSH_KEYGEN_OPTIONS="-b $SSH_ECDSA_BITS" + fi ;; *) # wrong argument exit 12 ;; esac @@ -25,7 +31,7 @@ fi rm -f $KEY{,.pub} # create new keys -if ! $KEYGEN -q -t $KEYTYPE -f $KEY -C '' -N '' >&/dev/null; then +if ! $KEYGEN -q -t $KEYTYPE $SSH_KEYGEN_OPTIONS -f $KEY -C '' -N '' >&/dev/null; then exit 1 fi diff --git a/sshd.sysconfig b/sshd.sysconfig index a217ce7cd60884dd4db85857dc33d3be5943ba01..ee44ae692ca9591f968a969467791b8ea9c035f4 100644 --- a/sshd.sysconfig +++ b/sshd.sysconfig @@ -5,3 +5,6 @@ # example using systemctl enable sshd-keygen@dsa.service to allow creation # of DSA key or systemctl mask sshd-keygen@rsa.service to disable RSA key # creation. + +#SSH_RSA_BITS=3072 +#SSH_ECDSA_BITS=256