diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:40:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:40:05 +0000 |
commit | e21fe8c3a4007c4a10f67e9e8107c3f083002f06 (patch) | |
tree | 926b3c2a4d3fd0decde08f6a4d22862ef03a7046 /debian/patches | |
parent | Adding upstream version 1:9.2p1. (diff) | |
download | openssh-debian/1%9.2p1-2+deb12u2.tar.xz openssh-debian/1%9.2p1-2+deb12u2.zip |
Adding debian version 1:9.2p1-2+deb12u2.debian/1%9.2p1-2+deb12u2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debian/patches')
36 files changed, 7968 insertions, 0 deletions
diff --git a/debian/patches/CVE-2023-28531.patch b/debian/patches/CVE-2023-28531.patch new file mode 100644 index 0000000..448632a --- /dev/null +++ b/debian/patches/CVE-2023-28531.patch @@ -0,0 +1,31 @@ +From d1461c936223751e723662115b12bb0e9ba96f65 Mon Sep 17 00:00:00 2001 +From: "djm@openbsd.org" <djm@openbsd.org> +Date: Thu, 9 Mar 2023 06:58:26 +0000 +Subject: upstream: include destination constraints for smartcard keys too. + +Spotted by Luci Stanescu; ok deraadt@ markus@ + +OpenBSD-Commit-ID: add879fac6903a1cb1d1e42c4309e5359c3d870f + +Bug-Debian: https://bugs.debian.org/1033166 +Origin: backport, https://anongit.mindrot.org/openssh.git/commit/?id=54ac4ab2b53ce9fcb66b8250dee91c070e4167ed +Last-Update: 2023-12-19 + +Patch-Name: CVE-2023-28531.patch +--- + authfd.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/authfd.c b/authfd.c +index b633e35ea..b9ae505ad 100644 +--- a/authfd.c ++++ b/authfd.c +@@ -666,7 +666,7 @@ ssh_update_card(int sock, int add, const char *reader_id, const char *pin, + struct dest_constraint **dest_constraints, size_t ndest_constraints) + { + struct sshbuf *msg; +- int r, constrained = (life || confirm); ++ int r, constrained = (life || confirm || dest_constraints); + u_char type; + + if (add) { diff --git a/debian/patches/CVE-2023-38408-1.patch b/debian/patches/CVE-2023-38408-1.patch new file mode 100644 index 0000000..9f6d1ec --- /dev/null +++ b/debian/patches/CVE-2023-38408-1.patch @@ -0,0 +1,30 @@ +From dee3878689aef5365955442869be02d420b65ea6 Mon Sep 17 00:00:00 2001 +From: Damien Miller <djm@mindrot.org> +Date: Thu, 13 Jul 2023 12:09:34 +1000 +Subject: terminate pkcs11 process for bad libraries + +Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=b23fe83f06ee7e721033769cfa03ae840476d280 +Last-Update: 2023-09-17 + +Patch-Name: CVE-2023-38408-1.patch +--- + ssh-pkcs11.c | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c +index b2e2b32a5..9e48c134e 100644 +--- a/ssh-pkcs11.c ++++ b/ssh-pkcs11.c +@@ -1537,10 +1537,8 @@ pkcs11_register_provider(char *provider_id, char *pin, + error("dlopen %s failed: %s", provider_id, dlerror()); + goto fail; + } +- if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL) { +- error("dlsym(C_GetFunctionList) failed: %s", dlerror()); +- goto fail; +- } ++ if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL) ++ fatal("dlsym(C_GetFunctionList) failed: %s", dlerror()); + p = xcalloc(1, sizeof(*p)); + p->name = xstrdup(provider_id); + p->handle = handle; diff --git a/debian/patches/CVE-2023-38408-2.patch b/debian/patches/CVE-2023-38408-2.patch new file mode 100644 index 0000000..c8f5980 --- /dev/null +++ b/debian/patches/CVE-2023-38408-2.patch @@ -0,0 +1,104 @@ +From 5c06b89189eb27f692b900526d60bf744918511e Mon Sep 17 00:00:00 2001 +From: Damien Miller <djm@mindrot.org> +Date: Fri, 7 Jul 2023 13:30:15 +1000 +Subject: disallow remote addition of FIDO/PKCS11 keys + +Depends on the local client performing the session-bind@openssh.com +operation, so non-OpenSSH local client may circumvent this. + +Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=d7790cdce72a1b6982795baa2b4d6f0bdbb0100d +Last-Update: 2023-09-17 + +Patch-Name: CVE-2023-38408-2.patch +--- + ssh-agent.1 | 22 ++++++++++++++++++++-- + ssh-agent.c | 21 ++++++++++++++++++++- + 2 files changed, 40 insertions(+), 3 deletions(-) + +diff --git a/ssh-agent.1 b/ssh-agent.1 +index ba418bb41..0e5b6c15c 100644 +--- a/ssh-agent.1 ++++ b/ssh-agent.1 +@@ -107,9 +107,27 @@ environment variable). + .It Fl O Ar option + Specify an option when starting + .Nm . +-Currently only one option is supported: ++Currently two options are supported: ++.Cm allow-remote-pkcs11 ++and + .Cm no-restrict-websafe . +-This instructs ++.Pp ++The ++.Cm allow-remote-pkcs11 ++option allows clients of a forwarded ++.Nm ++to load PKCS#11 or FIDO provider libraries. ++By default only local clients may perform this operation. ++Note that signalling that a ++.Nm ++client remote is performed by ++.Xr ssh 1 , ++and use of other tools to forward access to the agent socket may circumvent ++this restriction. ++.Pp ++The ++.Cm no-restrict-websafe , ++instructs + .Nm + to permit signatures using FIDO keys that might be web authentication + requests. +diff --git a/ssh-agent.c b/ssh-agent.c +index 63e1137bc..dce2849d8 100644 +--- a/ssh-agent.c ++++ b/ssh-agent.c +@@ -170,6 +170,12 @@ char socket_dir[PATH_MAX]; + /* Pattern-list of allowed PKCS#11/Security key paths */ + static char *allowed_providers; + ++/* ++ * Allows PKCS11 providers or SK keys that use non-internal providers to ++ * be added over a remote connection (identified by session-bind@openssh.com). ++ */ ++static int remote_add_provider; ++ + /* locking */ + #define LOCK_SIZE 32 + #define LOCK_SALT_SIZE 16 +@@ -1229,6 +1235,12 @@ process_add_identity(SocketEntry *e) + if (strcasecmp(sk_provider, "internal") == 0) { + debug_f("internal provider"); + } else { ++ if (e->nsession_ids != 0 && !remote_add_provider) { ++ verbose("failed add of SK provider \"%.100s\": " ++ "remote addition of providers is disabled", ++ sk_provider); ++ goto out; ++ } + if (realpath(sk_provider, canonical_provider) == NULL) { + verbose("failed provider \"%.100s\": " + "realpath: %s", sk_provider, +@@ -1392,6 +1404,11 @@ process_add_smartcard_key(SocketEntry *e) + error_f("failed to parse constraints"); + goto send; + } ++ if (e->nsession_ids != 0 && !remote_add_provider) { ++ verbose("failed PKCS#11 add of \"%.100s\": remote addition of " ++ "providers is disabled", provider); ++ goto send; ++ } + if (realpath(provider, canonical_provider) == NULL) { + verbose("failed PKCS#11 add of \"%.100s\": realpath: %s", + provider, strerror(errno)); +@@ -2052,7 +2069,9 @@ main(int ac, char **av) + break; + case 'O': + if (strcmp(optarg, "no-restrict-websafe") == 0) +- restrict_websafe = 0; ++ restrict_websafe = 0; ++ else if (strcmp(optarg, "allow-remote-pkcs11") == 0) ++ remote_add_provider = 1; + else + fatal("Unknown -O option"); + break; diff --git a/debian/patches/CVE-2023-38408-3.patch b/debian/patches/CVE-2023-38408-3.patch new file mode 100644 index 0000000..6089222 --- /dev/null +++ b/debian/patches/CVE-2023-38408-3.patch @@ -0,0 +1,174 @@ +From 29c7785a3673101b3af8f6f712795fa128e52ddd Mon Sep 17 00:00:00 2001 +From: "djm@openbsd.org" <djm@openbsd.org> +Date: Wed, 19 Jul 2023 14:02:27 +0000 +Subject: upstream: Ensure FIDO/PKCS11 libraries contain expected symbols + +This checks via nlist(3) that candidate provider libraries contain one +of the symbols that we will require prior to dlopen(), which can cause +a number of side effects, including execution of constructors. + +Feedback deraadt; ok markus + +OpenBSD-Commit-ID: 1508a5fbd74e329e69a55b56c453c292029aefbe + +Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=29ef8a04866ca14688d5b7fed7b8b9deab851f77 +Last-Update: 2023-09-17 + +Patch-Name: CVE-2023-38408-3.patch +--- + misc.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + misc.h | 1 + + ssh-pkcs11.c | 4 +++ + ssh-sk.c | 8 +++--- + 4 files changed, 86 insertions(+), 3 deletions(-) + +diff --git a/misc.c b/misc.c +index 2268d8875..36e72f5b5 100644 +--- a/misc.c ++++ b/misc.c +@@ -22,6 +22,7 @@ + + #include <sys/types.h> + #include <sys/ioctl.h> ++#include <sys/mman.h> + #include <sys/socket.h> + #include <sys/stat.h> + #include <sys/time.h> +@@ -35,6 +36,9 @@ + #ifdef HAVE_POLL_H + #include <poll.h> + #endif ++#ifdef HAVE_NLIST_H ++#include <nlist.h> ++#endif + #include <signal.h> + #include <stdarg.h> + #include <stdio.h> +@@ -2962,3 +2966,75 @@ ptimeout_isset(struct timespec *pt) + { + return pt->tv_sec != -1; + } ++ ++/* ++ * Returns zero if the library at 'path' contains symbol 's', nonzero ++ * otherwise. ++ */ ++int ++lib_contains_symbol(const char *path, const char *s) ++{ ++#ifdef HAVE_NLIST_H ++ struct nlist nl[2]; ++ int ret = -1, r; ++ ++ memset(nl, 0, sizeof(nl)); ++ nl[0].n_name = xstrdup(s); ++ nl[1].n_name = NULL; ++ if ((r = nlist(path, nl)) == -1) { ++ error_f("nlist failed for %s", path); ++ goto out; ++ } ++ if (r != 0 || nl[0].n_value == 0 || nl[0].n_type == 0) { ++ error_f("library %s does not contain symbol %s", path, s); ++ goto out; ++ } ++ /* success */ ++ ret = 0; ++ out: ++ free(nl[0].n_name); ++ return ret; ++#else /* HAVE_NLIST_H */ ++ int fd, ret = -1; ++ struct stat st; ++ void *m = NULL; ++ size_t sz = 0; ++ ++ memset(&st, 0, sizeof(st)); ++ if ((fd = open(path, O_RDONLY)) < 0) { ++ error_f("open %s: %s", path, strerror(errno)); ++ return -1; ++ } ++ if (fstat(fd, &st) != 0) { ++ error_f("fstat %s: %s", path, strerror(errno)); ++ goto out; ++ } ++ if (!S_ISREG(st.st_mode)) { ++ error_f("%s is not a regular file", path); ++ goto out; ++ } ++ if (st.st_size < 0 || ++ (size_t)st.st_size < strlen(s) || ++ st.st_size >= INT_MAX/2) { ++ error_f("%s bad size %lld", path, (long long)st.st_size); ++ goto out; ++ } ++ sz = (size_t)st.st_size; ++ if ((m = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED || ++ m == NULL) { ++ error_f("mmap %s: %s", path, strerror(errno)); ++ goto out; ++ } ++ if (memmem(m, sz, s, strlen(s)) == NULL) { ++ error_f("%s does not contain expected string %s", path, s); ++ goto out; ++ } ++ /* success */ ++ ret = 0; ++ out: ++ if (m != NULL && m != MAP_FAILED) ++ munmap(m, sz); ++ close(fd); ++ return ret; ++#endif /* HAVE_NLIST_H */ ++} +diff --git a/misc.h b/misc.h +index 31b6557c6..1a49e7cec 100644 +--- a/misc.h ++++ b/misc.h +@@ -96,6 +96,7 @@ int parse_absolute_time(const char *, uint64_t *); + void format_absolute_time(uint64_t, char *, size_t); + int path_absolute(const char *); + int stdfd_devnull(int, int, int); ++int lib_contains_symbol(const char *, const char *); + + void sock_set_v6only(int); + +diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c +index 9e48c134e..0aef37934 100644 +--- a/ssh-pkcs11.c ++++ b/ssh-pkcs11.c +@@ -1532,6 +1532,10 @@ pkcs11_register_provider(char *provider_id, char *pin, + debug_f("provider already registered: %s", provider_id); + goto fail; + } ++ if (lib_contains_symbol(provider_id, "C_GetFunctionList") != 0) { ++ error("provider %s is not a PKCS11 library", provider_id); ++ goto fail; ++ } + /* open shared pkcs11-library */ + if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) { + error("dlopen %s failed: %s", provider_id, dlerror()); +diff --git a/ssh-sk.c b/ssh-sk.c +index fbeb39320..d1c18803f 100644 +--- a/ssh-sk.c ++++ b/ssh-sk.c +@@ -1,4 +1,4 @@ +-/* $OpenBSD: ssh-sk.c,v 1.39 2022/07/20 03:29:14 djm Exp $ */ ++/* $OpenBSD: ssh-sk.c,v 1.40 2023/07/19 14:02:27 djm Exp $ */ + /* + * Copyright (c) 2019 Google LLC + * +@@ -133,10 +133,12 @@ sshsk_open(const char *path) + goto fail; + #endif + } +- if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL) { +- error("Provider \"%s\" dlopen failed: %s", path, dlerror()); ++ if (lib_contains_symbol(path, "sk_api_version") != 0) { ++ error("provider %s is not an OpenSSH FIDO library", path); + goto fail; + } ++ if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL) ++ fatal("Provider \"%s\" dlopen failed: %s", path, dlerror()); + if ((ret->sk_api_version = dlsym(ret->dlhandle, + "sk_api_version")) == NULL) { + error("Provider \"%s\" dlsym(sk_api_version) failed: %s", diff --git a/debian/patches/CVE-2023-48795.patch b/debian/patches/CVE-2023-48795.patch new file mode 100644 index 0000000..ef4eca1 --- /dev/null +++ b/debian/patches/CVE-2023-48795.patch @@ -0,0 +1,468 @@ +From 9148d0a8031d89f53f045b63ac3a709611d94778 Mon Sep 17 00:00:00 2001 +From: "djm@openbsd.org" <djm@openbsd.org> +Date: Mon, 18 Dec 2023 14:45:17 +0000 +Subject: upstream: implement "strict key exchange" in ssh and sshd + +This adds a protocol extension to improve the integrity of the SSH +transport protocol, particular in and around the initial key exchange +(KEX) phase. + +Full details of the extension are in the PROTOCOL file. + +with markus@ + +OpenBSD-Commit-ID: 2a66ac962f0a630d7945fee54004ed9e9c439f14 + +Origin: backport, https://anongit.mindrot.org/openssh.git/commit/?id=1edb00c58f8a6875fad6a497aa2bacf37f9e6cd5 +Last-Update: 2023-12-19 + +Patch-Name: CVE-2023-48795.patch +--- + PROTOCOL | 26 +++++++++++++++++ + kex.c | 72 +++++++++++++++++++++++++++++++---------------- + kex.h | 1 + + packet.c | 78 ++++++++++++++++++++++++++++++++++++++------------- + sshconnect2.c | 14 +++------ + sshd.c | 9 ++++-- + 6 files changed, 143 insertions(+), 57 deletions(-) + +diff --git a/PROTOCOL b/PROTOCOL +index 27804d0ca..ad611a01a 100644 +--- a/PROTOCOL ++++ b/PROTOCOL +@@ -104,6 +104,32 @@ http://git.libssh.org/users/aris/libssh.git/plain/doc/curve25519-sha256@libssh.o + + This is identical to curve25519-sha256 as later published in RFC8731. + ++1.9 transport: strict key exchange extension ++ ++OpenSSH supports a number of transport-layer hardening measures under ++a "strict KEX" feature. This feature is signalled similarly to the ++RFC8308 ext-info feature: by including a additional algorithm in the ++initiial SSH2_MSG_KEXINIT kex_algorithms field. The client may append ++"kex-strict-c-v00@openssh.com" to its kex_algorithms and the server ++may append "kex-strict-s-v00@openssh.com". These pseudo-algorithms ++are only valid in the initial SSH2_MSG_KEXINIT and MUST be ignored ++if they are present in subsequent SSH2_MSG_KEXINIT packets. ++ ++When an endpoint that supports this extension observes this algorithm ++name in a peer's KEXINIT packet, it MUST make the following changes to ++the the protocol: ++ ++a) During initial KEX, terminate the connection if any unexpected or ++ out-of-sequence packet is received. This includes terminating the ++ connection if the first packet received is not SSH2_MSG_KEXINIT. ++ Unexpected packets for the purpose of strict KEX include messages ++ that are otherwise valid at any time during the connection such as ++ SSH2_MSG_DEBUG and SSH2_MSG_IGNORE. ++b) After sending or receiving a SSH2_MSG_NEWKEYS message, reset the ++ packet sequence number to zero. This behaviour persists for the ++ duration of the connection (i.e. not just the first ++ SSH2_MSG_NEWKEYS). ++ + 2. Connection protocol changes + + 2.1. connection: Channel write close extension "eow@openssh.com" +diff --git a/kex.c b/kex.c +index e7b7316c7..0b4fc4767 100644 +--- a/kex.c ++++ b/kex.c +@@ -68,7 +68,7 @@ + #endif + + /* prototype */ +-static int kex_choose_conf(struct ssh *); ++static int kex_choose_conf(struct ssh *, uint32_t seq); + static int kex_input_newkeys(int, u_int32_t, struct ssh *); + + static const char * const proposal_names[PROPOSAL_MAX] = { +@@ -209,6 +209,18 @@ kex_names_valid(const char *names) + return 1; + } + ++/* returns non-zero if proposal contains any algorithm from algs */ ++static int ++has_any_alg(const char *proposal, const char *algs) ++{ ++ char *cp; ++ ++ if ((cp = match_list(proposal, algs, NULL)) == NULL) ++ return 0; ++ free(cp); ++ return 1; ++} ++ + /* + * Concatenate algorithm names, avoiding duplicates in the process. + * Caller must free returned string. +@@ -216,7 +228,7 @@ kex_names_valid(const char *names) + char * + kex_names_cat(const char *a, const char *b) + { +- char *ret = NULL, *tmp = NULL, *cp, *p, *m; ++ char *ret = NULL, *tmp = NULL, *cp, *p; + size_t len; + + if (a == NULL || *a == '\0') +@@ -233,10 +245,8 @@ kex_names_cat(const char *a, const char *b) + } + strlcpy(ret, a, len); + for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { +- if ((m = match_list(ret, p, NULL)) != NULL) { +- free(m); ++ if (has_any_alg(ret, p)) + continue; /* Algorithm already present */ +- } + if (strlcat(ret, ",", len) >= len || + strlcat(ret, p, len) >= len) { + free(tmp); +@@ -467,7 +477,12 @@ kex_protocol_error(int type, u_int32_t seq, struct ssh *ssh) + { + int r; + +- error("kex protocol error: type %d seq %u", type, seq); ++ /* If in strict mode, any unexpected message is an error */ ++ if ((ssh->kex->flags & KEX_INITIAL) && ssh->kex->kex_strict) { ++ ssh_packet_disconnect(ssh, "strict KEX violation: " ++ "unexpected packet type %u (seqnr %u)", type, seq); ++ } ++ error_f("type %u seq %u", type, seq); + if ((r = sshpkt_start(ssh, SSH2_MSG_UNIMPLEMENTED)) != 0 || + (r = sshpkt_put_u32(ssh, seq)) != 0 || + (r = sshpkt_send(ssh)) != 0) +@@ -542,6 +557,11 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) + ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &kex_protocol_error); + if ((r = sshpkt_get_u32(ssh, &ninfo)) != 0) + return r; ++ if (ninfo >= 1024) { ++ error("SSH2_MSG_EXT_INFO with too many entries, expected " ++ "<=1024, received %u", ninfo); ++ return dispatch_protocol_error(type, seq, ssh); ++ } + for (i = 0; i < ninfo; i++) { + if ((r = sshpkt_get_cstring(ssh, &name, NULL)) != 0) + return r; +@@ -657,7 +677,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh) + error_f("no kex"); + return SSH_ERR_INTERNAL_ERROR; + } +- ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_protocol_error); + ptr = sshpkt_ptr(ssh, &dlen); + if ((r = sshbuf_put(kex->peer, ptr, dlen)) != 0) + return r; +@@ -693,7 +713,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh) + if (!(kex->flags & KEX_INIT_SENT)) + if ((r = kex_send_kexinit(ssh)) != 0) + return r; +- if ((r = kex_choose_conf(ssh)) != 0) ++ if ((r = kex_choose_conf(ssh, seq)) != 0) + return r; + + if (kex->kex_type < KEX_MAX && kex->kex[kex->kex_type] != NULL) +@@ -960,20 +980,14 @@ proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX]) + return (1); + } + +-/* returns non-zero if proposal contains any algorithm from algs */ + static int +-has_any_alg(const char *proposal, const char *algs) ++kexalgs_contains(char **peer, const char *ext) + { +- char *cp; +- +- if ((cp = match_list(proposal, algs, NULL)) == NULL) +- return 0; +- free(cp); +- return 1; ++ return has_any_alg(peer[PROPOSAL_KEX_ALGS], ext); + } + + static int +-kex_choose_conf(struct ssh *ssh) ++kex_choose_conf(struct ssh *ssh, uint32_t seq) + { + struct kex *kex = ssh->kex; + struct newkeys *newkeys; +@@ -998,13 +1012,23 @@ kex_choose_conf(struct ssh *ssh) + sprop=peer; + } + +- /* Check whether client supports ext_info_c */ +- if (kex->server && (kex->flags & KEX_INITIAL)) { +- char *ext; +- +- ext = match_list("ext-info-c", peer[PROPOSAL_KEX_ALGS], NULL); +- kex->ext_info_c = (ext != NULL); +- free(ext); ++ /* Check whether peer supports ext_info/kex_strict */ ++ if ((kex->flags & KEX_INITIAL) != 0) { ++ if (kex->server) { ++ kex->ext_info_c = kexalgs_contains(peer, "ext-info-c"); ++ kex->kex_strict = kexalgs_contains(peer, ++ "kex-strict-c-v00@openssh.com"); ++ } else { ++ kex->kex_strict = kexalgs_contains(peer, ++ "kex-strict-s-v00@openssh.com"); ++ } ++ if (kex->kex_strict) { ++ debug3_f("will use strict KEX ordering"); ++ if (seq != 0) ++ ssh_packet_disconnect(ssh, ++ "strict KEX violation: " ++ "KEXINIT was not the first packet"); ++ } + } + + /* Check whether client supports rsa-sha2 algorithms */ +diff --git a/kex.h b/kex.h +index e356f5ed0..99b47435f 100644 +--- a/kex.h ++++ b/kex.h +@@ -157,6 +157,7 @@ struct kex { + u_int kex_type; + char *server_sig_algs; + int ext_info_c; ++ int kex_strict; + struct sshbuf *my; + struct sshbuf *peer; + struct sshbuf *client_version; +diff --git a/packet.c b/packet.c +index 3f64d2d32..25553fbb5 100644 +--- a/packet.c ++++ b/packet.c +@@ -1205,8 +1205,13 @@ ssh_packet_send2_wrapped(struct ssh *ssh) + sshbuf_dump(state->output, stderr); + #endif + /* increment sequence number for outgoing packets */ +- if (++state->p_send.seqnr == 0) ++ if (++state->p_send.seqnr == 0) { ++ if ((ssh->kex->flags & KEX_INITIAL) != 0) { ++ ssh_packet_disconnect(ssh, "outgoing sequence number " ++ "wrapped during initial key exchange"); ++ } + logit("outgoing seqnr wraps around"); ++ } + if (++state->p_send.packets == 0) + if (!(ssh->compat & SSH_BUG_NOREKEY)) + return SSH_ERR_NEED_REKEY; +@@ -1214,6 +1219,11 @@ ssh_packet_send2_wrapped(struct ssh *ssh) + state->p_send.bytes += len; + sshbuf_reset(state->outgoing_packet); + ++ if (type == SSH2_MSG_NEWKEYS && ssh->kex->kex_strict) { ++ debug_f("resetting send seqnr %u", state->p_send.seqnr); ++ state->p_send.seqnr = 0; ++ } ++ + if (type == SSH2_MSG_NEWKEYS) + r = ssh_set_newkeys(ssh, MODE_OUT); + else if (type == SSH2_MSG_USERAUTH_SUCCESS && state->server_side) +@@ -1342,8 +1352,7 @@ ssh_packet_read_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) + /* Stay in the loop until we have received a complete packet. */ + for (;;) { + /* Try to read a packet from the buffer. */ +- r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p); +- if (r != 0) ++ if ((r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p)) != 0) + break; + /* If we got a packet, return it. */ + if (*typep != SSH_MSG_NONE) +@@ -1627,10 +1636,16 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) + if ((r = sshbuf_consume(state->input, mac->mac_len)) != 0) + goto out; + } ++ + if (seqnr_p != NULL) + *seqnr_p = state->p_read.seqnr; +- if (++state->p_read.seqnr == 0) ++ if (++state->p_read.seqnr == 0) { ++ if ((ssh->kex->flags & KEX_INITIAL) != 0) { ++ ssh_packet_disconnect(ssh, "incoming sequence number " ++ "wrapped during initial key exchange"); ++ } + logit("incoming seqnr wraps around"); ++ } + if (++state->p_read.packets == 0) + if (!(ssh->compat & SSH_BUG_NOREKEY)) + return SSH_ERR_NEED_REKEY; +@@ -1696,6 +1711,10 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) + #endif + /* reset for next packet */ + state->packlen = 0; ++ if (*typep == SSH2_MSG_NEWKEYS && ssh->kex->kex_strict) { ++ debug_f("resetting read seqnr %u", state->p_read.seqnr); ++ state->p_read.seqnr = 0; ++ } + + if ((r = ssh_packet_check_rekey(ssh)) != 0) + return r; +@@ -1716,10 +1735,39 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) + r = ssh_packet_read_poll2(ssh, typep, seqnr_p); + if (r != 0) + return r; +- if (*typep) { +- state->keep_alive_timeouts = 0; +- DBG(debug("received packet type %d", *typep)); ++ if (*typep == 0) { ++ /* no message ready */ ++ return 0; ++ } ++ state->keep_alive_timeouts = 0; ++ DBG(debug("received packet type %d", *typep)); ++ ++ /* Always process disconnect messages */ ++ if (*typep == SSH2_MSG_DISCONNECT) { ++ if ((r = sshpkt_get_u32(ssh, &reason)) != 0 || ++ (r = sshpkt_get_string(ssh, &msg, NULL)) != 0) ++ return r; ++ /* Ignore normal client exit notifications */ ++ do_log2(ssh->state->server_side && ++ reason == SSH2_DISCONNECT_BY_APPLICATION ? ++ SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR, ++ "Received disconnect from %s port %d:" ++ "%u: %.400s", ssh_remote_ipaddr(ssh), ++ ssh_remote_port(ssh), reason, msg); ++ free(msg); ++ return SSH_ERR_DISCONNECTED; + } ++ ++ /* ++ * Do not implicitly handle any messages here during initial ++ * KEX when in strict mode. They will be need to be allowed ++ * explicitly by the KEX dispatch table or they will generate ++ * protocol errors. ++ */ ++ if (ssh->kex != NULL && ++ (ssh->kex->flags & KEX_INITIAL) && ssh->kex->kex_strict) ++ return 0; ++ /* Implicitly handle transport-level messages */ + switch (*typep) { + case SSH2_MSG_IGNORE: + debug3("Received SSH2_MSG_IGNORE"); +@@ -1734,19 +1782,6 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) + debug("Remote: %.900s", msg); + free(msg); + break; +- case SSH2_MSG_DISCONNECT: +- if ((r = sshpkt_get_u32(ssh, &reason)) != 0 || +- (r = sshpkt_get_string(ssh, &msg, NULL)) != 0) +- return r; +- /* Ignore normal client exit notifications */ +- do_log2(ssh->state->server_side && +- reason == SSH2_DISCONNECT_BY_APPLICATION ? +- SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR, +- "Received disconnect from %s port %d:" +- "%u: %.400s", ssh_remote_ipaddr(ssh), +- ssh_remote_port(ssh), reason, msg); +- free(msg); +- return SSH_ERR_DISCONNECTED; + case SSH2_MSG_UNIMPLEMENTED: + if ((r = sshpkt_get_u32(ssh, &seqnr)) != 0) + return r; +@@ -2211,6 +2246,7 @@ kex_to_blob(struct sshbuf *m, struct kex *kex) + (r = sshbuf_put_u32(m, kex->hostkey_type)) != 0 || + (r = sshbuf_put_u32(m, kex->hostkey_nid)) != 0 || + (r = sshbuf_put_u32(m, kex->kex_type)) != 0 || ++ (r = sshbuf_put_u32(m, kex->kex_strict)) != 0 || + (r = sshbuf_put_stringb(m, kex->my)) != 0 || + (r = sshbuf_put_stringb(m, kex->peer)) != 0 || + (r = sshbuf_put_stringb(m, kex->client_version)) != 0 || +@@ -2373,6 +2409,7 @@ kex_from_blob(struct sshbuf *m, struct kex **kexp) + (r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_type)) != 0 || + (r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_nid)) != 0 || + (r = sshbuf_get_u32(m, &kex->kex_type)) != 0 || ++ (r = sshbuf_get_u32(m, &kex->kex_strict)) != 0 || + (r = sshbuf_get_stringb(m, kex->my)) != 0 || + (r = sshbuf_get_stringb(m, kex->peer)) != 0 || + (r = sshbuf_get_stringb(m, kex->client_version)) != 0 || +@@ -2701,6 +2738,7 @@ sshpkt_disconnect(struct ssh *ssh, const char *fmt,...) + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + ++ debug2_f("sending SSH2_MSG_DISCONNECT: %s", buf); + if ((r = sshpkt_start(ssh, SSH2_MSG_DISCONNECT)) != 0 || + (r = sshpkt_put_u32(ssh, SSH2_DISCONNECT_PROTOCOL_ERROR)) != 0 || + (r = sshpkt_put_cstring(ssh, buf)) != 0 || +diff --git a/sshconnect2.c b/sshconnect2.c +index cb6a94e76..3e5f69470 100644 +--- a/sshconnect2.c ++++ b/sshconnect2.c +@@ -250,7 +250,8 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port, + fatal_fr(r, "kex_assemble_namelist"); + free(all_key); + +- if ((s = kex_names_cat(options.kex_algorithms, "ext-info-c")) == NULL) ++ if ((s = kex_names_cat(options.kex_algorithms, ++ "ext-info-c,kex-strict-c-v00@openssh.com")) == NULL) + fatal_f("kex_names_cat"); + myproposal[PROPOSAL_KEX_ALGS] = prop_kex = compat_kex_proposal(ssh, s); + myproposal[PROPOSAL_ENC_ALGS_CTOS] = +@@ -441,7 +442,6 @@ struct cauthmethod { + }; + + static int input_userauth_service_accept(int, u_int32_t, struct ssh *); +-static int input_userauth_ext_info(int, u_int32_t, struct ssh *); + static int input_userauth_success(int, u_int32_t, struct ssh *); + static int input_userauth_failure(int, u_int32_t, struct ssh *); + static int input_userauth_banner(int, u_int32_t, struct ssh *); +@@ -561,7 +561,7 @@ ssh_userauth2(struct ssh *ssh, const char *local_user, + + ssh->authctxt = &authctxt; + ssh_dispatch_init(ssh, &input_userauth_error); +- ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &input_userauth_ext_info); ++ ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, kex_input_ext_info); + ssh_dispatch_set(ssh, SSH2_MSG_SERVICE_ACCEPT, &input_userauth_service_accept); + ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &authctxt.success); /* loop until success */ + pubkey_cleanup(ssh); +@@ -613,13 +613,6 @@ input_userauth_service_accept(int type, u_int32_t seq, struct ssh *ssh) + return r; + } + +-/* ARGSUSED */ +-static int +-input_userauth_ext_info(int type, u_int32_t seqnr, struct ssh *ssh) +-{ +- return kex_input_ext_info(type, seqnr, ssh); +-} +- + void + userauth(struct ssh *ssh, char *authlist) + { +@@ -701,6 +694,7 @@ input_userauth_success(int type, u_int32_t seq, struct ssh *ssh) + free(authctxt->methoddata); + authctxt->methoddata = NULL; + authctxt->success = 1; /* break out */ ++ ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, dispatch_protocol_error); + return 0; + } + +diff --git a/sshd.c b/sshd.c +index fc22fcb62..dd8fd94a3 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -2471,11 +2471,13 @@ do_ssh2_kex(struct ssh *ssh) + { + char *myproposal[PROPOSAL_MAX] = { KEX_SERVER }; + struct kex *kex; +- char *prop_kex = NULL, *prop_enc = NULL, *prop_hostkey = NULL; ++ char *s, *prop_kex = NULL, *prop_enc = NULL, *prop_hostkey = NULL; + int r; + +- myproposal[PROPOSAL_KEX_ALGS] = prop_kex = compat_kex_proposal(ssh, +- options.kex_algorithms); ++ if ((s = kex_names_cat(options.kex_algorithms, ++ "kex-strict-s-v00@openssh.com")) == NULL) ++ fatal_f("kex_names_cat"); ++ myproposal[PROPOSAL_KEX_ALGS] = prop_kex = compat_kex_proposal(ssh, s); + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_STOC] = prop_enc = + compat_cipher_proposal(ssh, options.ciphers); +@@ -2583,6 +2585,7 @@ do_ssh2_kex(struct ssh *ssh) + free(prop_kex); + free(prop_enc); + free(prop_hostkey); ++ free(s); + debug("KEX done"); + } + diff --git a/debian/patches/CVE-2023-51384.patch b/debian/patches/CVE-2023-51384.patch new file mode 100644 index 0000000..1953197 --- /dev/null +++ b/debian/patches/CVE-2023-51384.patch @@ -0,0 +1,168 @@ +From d5be669c872a313a71d60babee64f3a80340dc51 Mon Sep 17 00:00:00 2001 +From: "djm@openbsd.org" <djm@openbsd.org> +Date: Mon, 18 Dec 2023 14:46:12 +0000 +Subject: upstream: apply destination constraints to all p11 keys + +Previously applied only to the first key returned from each token. + +ok markus@ + +OpenBSD-Commit-ID: 36df3afb8eb94eec6b2541f063d0d164ef8b488d + +Origin: backport, https://anongit.mindrot.org/openssh.git/commit/?id=881d9c6af9da4257c69c327c4e2f1508b2fa754b +Last-Update: 2023-12-19 + +Patch-Name: CVE-2023-51384.patch +--- + ssh-agent.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 99 insertions(+), 4 deletions(-) + +diff --git a/ssh-agent.c b/ssh-agent.c +index dce2849d8..27bf9b5ad 100644 +--- a/ssh-agent.c ++++ b/ssh-agent.c +@@ -248,6 +248,91 @@ free_dest_constraints(struct dest_constraint *dcs, size_t ndcs) + free(dcs); + } + ++static void ++dup_dest_constraint_hop(const struct dest_constraint_hop *dch, ++ struct dest_constraint_hop *out) ++{ ++ u_int i; ++ int r; ++ ++ out->user = dch->user == NULL ? NULL : xstrdup(dch->user); ++ out->hostname = dch->hostname == NULL ? NULL : xstrdup(dch->hostname); ++ out->is_ca = dch->is_ca; ++ out->nkeys = dch->nkeys; ++ out->keys = out->nkeys == 0 ? NULL : ++ xcalloc(out->nkeys, sizeof(*out->keys)); ++ out->key_is_ca = out->nkeys == 0 ? NULL : ++ xcalloc(out->nkeys, sizeof(*out->key_is_ca)); ++ for (i = 0; i < dch->nkeys; i++) { ++ if (dch->keys[i] != NULL && ++ (r = sshkey_from_private(dch->keys[i], ++ &(out->keys[i]))) != 0) ++ fatal_fr(r, "copy key"); ++ out->key_is_ca[i] = dch->key_is_ca[i]; ++ } ++} ++ ++static struct dest_constraint * ++dup_dest_constraints(const struct dest_constraint *dcs, size_t ndcs) ++{ ++ size_t i; ++ struct dest_constraint *ret; ++ ++ if (ndcs == 0) ++ return NULL; ++ ret = xcalloc(ndcs, sizeof(*ret)); ++ for (i = 0; i < ndcs; i++) { ++ dup_dest_constraint_hop(&dcs[i].from, &ret[i].from); ++ dup_dest_constraint_hop(&dcs[i].to, &ret[i].to); ++ } ++ return ret; ++} ++ ++#ifdef DEBUG_CONSTRAINTS ++static void ++dump_dest_constraint_hop(const struct dest_constraint_hop *dch) ++{ ++ u_int i; ++ char *fp; ++ ++ debug_f("user %s hostname %s is_ca %d nkeys %u", ++ dch->user == NULL ? "(null)" : dch->user, ++ dch->hostname == NULL ? "(null)" : dch->hostname, ++ dch->is_ca, dch->nkeys); ++ for (i = 0; i < dch->nkeys; i++) { ++ fp = NULL; ++ if (dch->keys[i] != NULL && ++ (fp = sshkey_fingerprint(dch->keys[i], ++ SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL) ++ fatal_f("fingerprint failed"); ++ debug_f("key %u/%u: %s%s%s key_is_ca %d", i, dch->nkeys, ++ dch->keys[i] == NULL ? "" : sshkey_ssh_name(dch->keys[i]), ++ dch->keys[i] == NULL ? "" : " ", ++ dch->keys[i] == NULL ? "none" : fp, ++ dch->key_is_ca[i]); ++ free(fp); ++ } ++} ++#endif /* DEBUG_CONSTRAINTS */ ++ ++static void ++dump_dest_constraints(const char *context, ++ const struct dest_constraint *dcs, size_t ndcs) ++{ ++#ifdef DEBUG_CONSTRAINTS ++ size_t i; ++ ++ debug_f("%s: %zu constraints", context, ndcs); ++ for (i = 0; i < ndcs; i++) { ++ debug_f("constraint %zu / %zu: from: ", i, ndcs); ++ dump_dest_constraint_hop(&dcs[i].from); ++ debug_f("constraint %zu / %zu: to: ", i, ndcs); ++ dump_dest_constraint_hop(&dcs[i].to); ++ } ++ debug_f("done for %s", context); ++#endif /* DEBUG_CONSTRAINTS */ ++} ++ + static void + free_identity(Identity *id) + { +@@ -519,13 +604,22 @@ process_request_identities(SocketEntry *e) + Identity *id; + struct sshbuf *msg, *keys; + int r; +- u_int nentries = 0; ++ u_int i = 0, nentries = 0; ++ char *fp; + + debug2_f("entering"); + + if ((msg = sshbuf_new()) == NULL || (keys = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); + TAILQ_FOREACH(id, &idtab->idlist, next) { ++ if ((fp = sshkey_fingerprint(id->key, SSH_FP_HASH_DEFAULT, ++ SSH_FP_DEFAULT)) == NULL) ++ fatal_f("fingerprint failed"); ++ debug_f("key %u / %u: %s %s", i++, idtab->nentries, ++ sshkey_ssh_name(id->key), fp); ++ dump_dest_constraints(__func__, ++ id->dest_constraints, id->ndest_constraints); ++ free(fp); + /* identity not visible, don't include in response */ + if (identity_permitted(id, e, NULL, NULL, NULL) != 0) + continue; +@@ -1225,6 +1319,7 @@ process_add_identity(SocketEntry *e) + sshbuf_reset(e->request); + goto out; + } ++ dump_dest_constraints(__func__, dest_constraints, ndest_constraints); + + if (sk_provider != NULL) { + if (!sshkey_is_sk(k)) { +@@ -1404,6 +1499,7 @@ process_add_smartcard_key(SocketEntry *e) + error_f("failed to parse constraints"); + goto send; + } ++ dump_dest_constraints(__func__, dest_constraints, ndest_constraints); + if (e->nsession_ids != 0 && !remote_add_provider) { + verbose("failed PKCS#11 add of \"%.100s\": remote addition of " + "providers is disabled", provider); +@@ -1439,10 +1535,9 @@ process_add_smartcard_key(SocketEntry *e) + } + id->death = death; + id->confirm = confirm; +- id->dest_constraints = dest_constraints; ++ id->dest_constraints = dup_dest_constraints( ++ dest_constraints, ndest_constraints); + id->ndest_constraints = ndest_constraints; +- dest_constraints = NULL; /* transferred */ +- ndest_constraints = 0; + TAILQ_INSERT_TAIL(&idtab->idlist, id, next); + idtab->nentries++; + success = 1; diff --git a/debian/patches/CVE-2023-51385.patch b/debian/patches/CVE-2023-51385.patch new file mode 100644 index 0000000..714c7a0 --- /dev/null +++ b/debian/patches/CVE-2023-51385.patch @@ -0,0 +1,94 @@ +From 14c4d6f0fa446414d1c38ad083107576d0ae3032 Mon Sep 17 00:00:00 2001 +From: "djm@openbsd.org" <djm@openbsd.org> +Date: Mon, 18 Dec 2023 14:47:44 +0000 +Subject: upstream: ban user/hostnames with most shell metacharacters + +This makes ssh(1) refuse user or host names provided on the +commandline that contain most shell metacharacters. + +Some programs that invoke ssh(1) using untrusted data do not filter +metacharacters in arguments they supply. This could create +interactions with user-specified ProxyCommand and other directives +that allow shell injection attacks to occur. + +It's a mistake to invoke ssh(1) with arbitrary untrusted arguments, +but getting this stuff right can be tricky, so this should prevent +most obvious ways of creating risky situations. It however is not +and cannot be perfect: ssh(1) has no practical way of interpreting +what shell quoting rules are in use and how they interact with the +user's specified ProxyCommand. + +To allow configurations that use strange user or hostnames to +continue to work, this strictness is applied only to names coming +from the commandline. Names specified using User or Hostname +directives in ssh_config(5) are not affected. + +feedback/ok millert@ markus@ dtucker@ deraadt@ + +OpenBSD-Commit-ID: 3b487348b5964f3e77b6b4d3da4c3b439e94b2d9 + +Origin: backport, https://anongit.mindrot.org/openssh.git/commit/?id=7ef3787c84b6b524501211b11a26c742f829af1a +Last-Update: 2023-12-19 + +Patch-Name: CVE-2023-51385.patch +--- + ssh.c | 39 +++++++++++++++++++++++++++++++++++++++ + 1 file changed, 39 insertions(+) + +diff --git a/ssh.c b/ssh.c +index 8b6b54558..422405035 100644 +--- a/ssh.c ++++ b/ssh.c +@@ -623,6 +623,41 @@ ssh_conn_info_free(struct ssh_conn_info *cinfo) + free(cinfo); + } + ++static int ++valid_hostname(const char *s) ++{ ++ size_t i; ++ ++ if (*s == '-') ++ return 0; ++ for (i = 0; s[i] != 0; i++) { ++ if (strchr("'`\"$\\;&<>|(){}", s[i]) != NULL || ++ isspace((u_char)s[i]) || iscntrl((u_char)s[i])) ++ return 0; ++ } ++ return 1; ++} ++ ++static int ++valid_ruser(const char *s) ++{ ++ size_t i; ++ ++ if (*s == '-') ++ return 0; ++ for (i = 0; s[i] != 0; i++) { ++ if (strchr("'`\";&<>|(){}", s[i]) != NULL) ++ return 0; ++ /* Disallow '-' after whitespace */ ++ if (isspace((u_char)s[i]) && s[i + 1] == '-') ++ return 0; ++ /* Disallow \ in last position */ ++ if (s[i] == '\\' && s[i + 1] == '\0') ++ return 0; ++ } ++ return 1; ++} ++ + /* + * Main program for the ssh client. + */ +@@ -1110,6 +1145,10 @@ main(int ac, char **av) + if (!host) + usage(); + ++ if (!valid_hostname(host)) ++ fatal("hostname contains invalid characters"); ++ if (options.user != NULL && !valid_ruser(options.user)) ++ fatal("remote username contains invalid characters"); + options.host_arg = xstrdup(host); + + /* Initialize the command to execute on remote host. */ diff --git a/debian/patches/authorized-keys-man-symlink.patch b/debian/patches/authorized-keys-man-symlink.patch new file mode 100644 index 0000000..46657c9 --- /dev/null +++ b/debian/patches/authorized-keys-man-symlink.patch @@ -0,0 +1,26 @@ +From 374a21e4acc5b06719640c0d6b82afdf4182b900 Mon Sep 17 00:00:00 2001 +From: Tomas Pospisek <tpo_deb@sourcepole.ch> +Date: Sun, 9 Feb 2014 16:10:07 +0000 +Subject: Install authorized_keys(5) as a symlink to sshd(8) + +Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1720 +Bug-Debian: http://bugs.debian.org/441817 +Last-Update: 2013-09-14 + +Patch-Name: authorized-keys-man-symlink.patch +--- + Makefile.in | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/Makefile.in b/Makefile.in +index 32b607719..85c6ebeb5 100644 +--- a/Makefile.in ++++ b/Makefile.in +@@ -415,6 +415,7 @@ install-files: + $(INSTALL) -m 644 sshd_config.5.out $(DESTDIR)$(mandir)/$(mansubdir)5/sshd_config.5 + $(INSTALL) -m 644 ssh_config.5.out $(DESTDIR)$(mandir)/$(mansubdir)5/ssh_config.5 + $(INSTALL) -m 644 sshd.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8 ++ ln -s ../$(mansubdir)8/sshd.8 $(DESTDIR)$(mandir)/$(mansubdir)5/authorized_keys.5 + $(INSTALL) -m 644 sftp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1 + $(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 + $(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8 diff --git a/debian/patches/conch-ssh-rsa.patch b/debian/patches/conch-ssh-rsa.patch new file mode 100644 index 0000000..09981a5 --- /dev/null +++ b/debian/patches/conch-ssh-rsa.patch @@ -0,0 +1,42 @@ +From 2df31e50f4cd159978c99055ed2d54b98a5ec7e4 Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Tue, 15 Feb 2022 18:25:35 +0000 +Subject: Work around RSA SHA-2 signature issues in conch + +This was supposed to be fixed in Twisted upstream +(https://twistedmatrix.com/trac/ticket/9765), and that fix is in Debian +now. However, regression tests still seem to fail in GitLab CI but not +locally (see e.g. +https://salsa.debian.org/ssh-team/openssh/-/jobs/3513178). Leave this +in place for now until we figure out what's wrong. + +Forwarded: not-needed +Last-Update: 2022-11-14 + +Patch-Name: conch-ssh-rsa.patch +--- + regress/test-exec.sh | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/regress/test-exec.sh b/regress/test-exec.sh +index a56108179..45ef82314 100644 +--- a/regress/test-exec.sh ++++ b/regress/test-exec.sh +@@ -682,6 +682,17 @@ REGRESS_INTEROP_CONCH=no + if test -x "$CONCH" ; then + REGRESS_INTEROP_CONCH=yes + fi ++case "$SCRIPT" in ++*conch*) ;; ++*) REGRESS_INTEROP_CONCH=no ++esac ++ ++if test "$REGRESS_INTEROP_CONCH" = "yes" ; then ++ # Work around missing support for RSA SHA-2 signatures: ++ # https://twistedmatrix.com/trac/ticket/9765 ++ echo HostKeyAlgorithms +ssh-rsa >> $OBJ/sshd_config ++ echo PubkeyAcceptedAlgorithms +ssh-rsa >> $OBJ/sshd_config ++fi + + # If PuTTY is present, new enough and we are running a PuTTY test, prepare + # keys and configuration. diff --git a/debian/patches/debian-banner.patch b/debian/patches/debian-banner.patch new file mode 100644 index 0000000..2c32879 --- /dev/null +++ b/debian/patches/debian-banner.patch @@ -0,0 +1,162 @@ +From 2d3ac49df11f0aed81f35ce9588eb2c578ec98f2 Mon Sep 17 00:00:00 2001 +From: Kees Cook <kees@debian.org> +Date: Sun, 9 Feb 2014 16:10:06 +0000 +Subject: Add DebianBanner server configuration option + +Setting this to "no" causes sshd to omit the Debian revision from its +initial protocol handshake, for those scared by package-versioning.patch. + +Bug-Debian: http://bugs.debian.org/562048 +Forwarded: not-needed +Last-Update: 2023-01-02 + +Patch-Name: debian-banner.patch +--- + kex.c | 5 +++-- + kex.h | 2 +- + servconf.c | 9 +++++++++ + servconf.h | 2 ++ + sshconnect.c | 2 +- + sshd.c | 2 +- + sshd_config.5 | 5 +++++ + 7 files changed, 22 insertions(+), 5 deletions(-) + +diff --git a/kex.c b/kex.c +index 32858e65c..e7b7316c7 100644 +--- a/kex.c ++++ b/kex.c +@@ -1272,7 +1272,7 @@ send_error(struct ssh *ssh, char *msg) + */ + int + kex_exchange_identification(struct ssh *ssh, int timeout_ms, +- const char *version_addendum) ++ int debian_banner, const char *version_addendum) + { + int remote_major, remote_minor, mismatch, oerrno = 0; + size_t len, n; +@@ -1290,7 +1290,8 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms, + if (version_addendum != NULL && *version_addendum == '\0') + version_addendum = NULL; + if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n", +- PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_RELEASE, ++ PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, ++ debian_banner ? SSH_RELEASE : SSH_RELEASE_MINIMUM, + version_addendum == NULL ? "" : " ", + version_addendum == NULL ? "" : version_addendum)) != 0) { + oerrno = errno; +diff --git a/kex.h b/kex.h +index 240dca89d..e356f5ed0 100644 +--- a/kex.h ++++ b/kex.h +@@ -200,7 +200,7 @@ char *kex_names_cat(const char *, const char *); + int kex_assemble_names(char **, const char *, const char *); + int kex_gss_names_valid(const char *); + +-int kex_exchange_identification(struct ssh *, int, const char *); ++int kex_exchange_identification(struct ssh *, int, int, const char *); + + struct kex *kex_new(void); + int kex_ready(struct ssh *, char *[PROPOSAL_MAX]); +diff --git a/servconf.c b/servconf.c +index 6529f2736..4ecbca8ed 100644 +--- a/servconf.c ++++ b/servconf.c +@@ -203,6 +203,7 @@ initialize_server_options(ServerOptions *options) + options->channel_timeouts = NULL; + options->num_channel_timeouts = 0; + options->unused_connection_timeout = -1; ++ options->debian_banner = -1; + } + + /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ +@@ -461,6 +462,8 @@ fill_default_server_options(ServerOptions *options) + options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE; + if (options->unused_connection_timeout == -1) + options->unused_connection_timeout = 0; ++ if (options->debian_banner == -1) ++ options->debian_banner = 1; + + assemble_algorithms(options); + +@@ -546,6 +549,7 @@ typedef enum { + sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding, + sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, + sRequiredRSASize, sChannelTimeout, sUnusedConnectionTimeout, ++ sDebianBanner, + sDeprecated, sIgnore, sUnsupported + } ServerOpCodes; + +@@ -719,6 +723,7 @@ static struct { + { "requiredrsasize", sRequiredRSASize, SSHCFG_ALL }, + { "channeltimeout", sChannelTimeout, SSHCFG_ALL }, + { "unusedconnectiontimeout", sUnusedConnectionTimeout, SSHCFG_ALL }, ++ { "debianbanner", sDebianBanner, SSHCFG_GLOBAL }, + { NULL, sBadOption, 0 } + }; + +@@ -2596,6 +2601,10 @@ process_server_config_line_depth(ServerOptions *options, char *line, + } + goto parse_time; + ++ case sDebianBanner: ++ intptr = &options->debian_banner; ++ goto parse_flag; ++ + case sDeprecated: + case sIgnore: + case sUnsupported: +diff --git a/servconf.h b/servconf.h +index 38a68d576..e142cf77a 100644 +--- a/servconf.h ++++ b/servconf.h +@@ -238,6 +238,8 @@ typedef struct { + u_int num_channel_timeouts; + + int unused_connection_timeout; ++ ++ int debian_banner; + } ServerOptions; + + /* Information about the incoming connection as used by Match */ +diff --git a/sshconnect.c b/sshconnect.c +index 77a3080ba..dbb3c98a7 100644 +--- a/sshconnect.c ++++ b/sshconnect.c +@@ -1574,7 +1574,7 @@ ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost, + lowercase(host); + + /* Exchange protocol version identification strings with the server. */ +- if ((r = kex_exchange_identification(ssh, timeout_ms, NULL)) != 0) ++ if ((r = kex_exchange_identification(ssh, timeout_ms, 1, NULL)) != 0) + sshpkt_fatal(ssh, r, "banner exchange"); + + /* Put the connection into non-blocking mode. */ +diff --git a/sshd.c b/sshd.c +index dd4a19131..b4ee435e5 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -2238,7 +2238,7 @@ main(int ac, char **av) + if (!debug_flag) + alarm(options.login_grace_time); + +- if ((r = kex_exchange_identification(ssh, -1, ++ if ((r = kex_exchange_identification(ssh, -1, options.debian_banner, + options.version_addendum)) != 0) + sshpkt_fatal(ssh, r, "banner exchange"); + +diff --git a/sshd_config.5 b/sshd_config.5 +index eb58750a5..2c6ec76cb 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -615,6 +615,11 @@ or + .Cm no . + The default is + .Cm yes . ++.It Cm DebianBanner ++Specifies whether the distribution-specified extra version suffix is ++included during initial protocol handshake. ++The default is ++.Cm yes . + .It Cm DenyGroups + This keyword can be followed by a list of group name patterns, separated + by spaces. diff --git a/debian/patches/debian-config.patch b/debian/patches/debian-config.patch new file mode 100644 index 0000000..f32c3fd --- /dev/null +++ b/debian/patches/debian-config.patch @@ -0,0 +1,285 @@ +From aedb5d2ee2799e3a95b6913721533d2c42c496b3 Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Sun, 9 Feb 2014 16:10:18 +0000 +Subject: Various Debian-specific configuration changes + +ssh: Enable ForwardX11Trusted, returning to earlier semantics which cause +fewer problems with existing setups (http://bugs.debian.org/237021). + +ssh: Set 'SendEnv LANG LC_*' by default (http://bugs.debian.org/264024). + +ssh: Enable HashKnownHosts by default to try to limit the spread of ssh +worms. + +ssh: Enable GSSAPIAuthentication by default. + +ssh: Include /etc/ssh/ssh_config.d/*.conf. + +sshd: Enable PAM, disable KbdInteractiveAuthentication, and disable +PrintMotd. + +sshd: Enable X11Forwarding. + +sshd: Set 'AcceptEnv LANG LC_*' by default. + +sshd: Change sftp subsystem path to /usr/lib/openssh/sftp-server. + +sshd: Include /etc/ssh/sshd_config.d/*.conf. + +regress: Run tests with 'UsePAM yes', to match sshd_config. + +Document all of this. + +Author: Russ Allbery <rra@debian.org> +Forwarded: not-needed +Last-Update: 2023-01-03 + +Patch-Name: debian-config.patch +--- + readconf.c | 2 +- + regress/test-exec.sh | 1 + + ssh.1 | 24 ++++++++++++++++++++++++ + ssh_config | 8 +++++++- + ssh_config.5 | 26 +++++++++++++++++++++++++- + sshd_config | 18 ++++++++++++------ + sshd_config.5 | 29 +++++++++++++++++++++++++++++ + 7 files changed, 99 insertions(+), 9 deletions(-) + +diff --git a/readconf.c b/readconf.c +index 18e814039..6c8ad919f 100644 +--- a/readconf.c ++++ b/readconf.c +@@ -2536,7 +2536,7 @@ fill_default_options(Options * options) + if (options->forward_x11 == -1) + options->forward_x11 = 0; + if (options->forward_x11_trusted == -1) +- options->forward_x11_trusted = 0; ++ options->forward_x11_trusted = 1; + if (options->forward_x11_timeout == -1) + options->forward_x11_timeout = 1200; + /* +diff --git a/regress/test-exec.sh b/regress/test-exec.sh +index df43f0214..a56108179 100644 +--- a/regress/test-exec.sh ++++ b/regress/test-exec.sh +@@ -539,6 +539,7 @@ cat << EOF > $OBJ/sshd_config + AcceptEnv _XXX_TEST_* + AcceptEnv _XXX_TEST + Subsystem sftp $SFTPSERVER ++ UsePAM yes + EOF + + # This may be necessary if /usr/src and/or /usr/obj are group-writable, +diff --git a/ssh.1 b/ssh.1 +index d07cd7840..395abd40b 100644 +--- a/ssh.1 ++++ b/ssh.1 +@@ -847,6 +847,16 @@ directive in + .Xr ssh_config 5 + for more information. + .Pp ++(Debian-specific: X11 forwarding is not subjected to X11 SECURITY extension ++restrictions by default, because too many programs currently crash in this ++mode. ++Set the ++.Cm ForwardX11Trusted ++option to ++.Dq no ++to restore the upstream behaviour. ++This may change in future depending on client-side improvements.) ++.Pp + .It Fl x + Disables X11 forwarding. + .Pp +@@ -855,6 +865,20 @@ Enables trusted X11 forwarding. + Trusted X11 forwardings are not subjected to the X11 SECURITY extension + controls. + .Pp ++(Debian-specific: In the default configuration, this option is equivalent to ++.Fl X , ++since ++.Cm ForwardX11Trusted ++defaults to ++.Dq yes ++as described above. ++Set the ++.Cm ForwardX11Trusted ++option to ++.Dq no ++to restore the upstream behaviour. ++This may change in future depending on client-side improvements.) ++.Pp + .It Fl y + Send log information using the + .Xr syslog 3 +diff --git a/ssh_config b/ssh_config +index 52aae8692..09a17cf18 100644 +--- a/ssh_config ++++ b/ssh_config +@@ -17,9 +17,12 @@ + # list of available options, their meanings and defaults, please see the + # ssh_config(5) man page. + +-# Host * ++Include /etc/ssh/ssh_config.d/*.conf ++ ++Host * + # ForwardAgent no + # ForwardX11 no ++# ForwardX11Trusted yes + # PasswordAuthentication yes + # HostbasedAuthentication no + # GSSAPIAuthentication no +@@ -46,3 +49,6 @@ + # ProxyCommand ssh -q -W %h:%p gateway.example.com + # RekeyLimit 1G 1h + # UserKnownHostsFile ~/.ssh/known_hosts.d/%k ++ SendEnv LANG LC_* ++ HashKnownHosts yes ++ GSSAPIAuthentication yes +diff --git a/ssh_config.5 b/ssh_config.5 +index 82d68650e..32851c984 100644 +--- a/ssh_config.5 ++++ b/ssh_config.5 +@@ -71,6 +71,29 @@ Since the first obtained value for each parameter is used, more + host-specific declarations should be given near the beginning of the + file, and general defaults at the end. + .Pp ++Note that the Debian ++.Ic openssh-client ++package sets several options as standard in ++.Pa /etc/ssh/ssh_config ++which are not the default in ++.Xr ssh 1 : ++.Pp ++.Bl -bullet -offset indent -compact ++.It ++.Cm Include /etc/ssh/ssh_config.d/*.conf ++.It ++.Cm SendEnv No LANG LC_* ++.It ++.Cm HashKnownHosts No yes ++.It ++.Cm GSSAPIAuthentication No yes ++.El ++.Pp ++.Pa /etc/ssh/ssh_config.d/*.conf ++files are included at the start of the system-wide configuration file, so ++options set there will override those in ++.Pa /etc/ssh/ssh_config. ++.Pp + The file contains keyword-argument pairs, one per line. + Lines starting with + .Ql # +@@ -802,11 +825,12 @@ elapsed. + .It Cm ForwardX11Trusted + If this option is set to + .Cm yes , ++(the Debian-specific default), + remote X11 clients will have full access to the original X11 display. + .Pp + If this option is set to + .Cm no +-(the default), ++(the upstream default), + remote X11 clients will be considered untrusted and prevented + from stealing or tampering with data belonging to trusted X11 + clients. +diff --git a/sshd_config b/sshd_config +index ecfe8d026..677f97d5d 100644 +--- a/sshd_config ++++ b/sshd_config +@@ -10,6 +10,8 @@ + # possible, but leave them commented. Uncommented options override the + # default value. + ++Include /etc/ssh/sshd_config.d/*.conf ++ + #Port 22 + #AddressFamily any + #ListenAddress 0.0.0.0 +@@ -57,8 +59,9 @@ AuthorizedKeysFile .ssh/authorized_keys + #PasswordAuthentication yes + #PermitEmptyPasswords no + +-# Change to no to disable s/key passwords +-#KbdInteractiveAuthentication yes ++# Change to yes to enable challenge-response passwords (beware issues with ++# some PAM modules and threads) ++KbdInteractiveAuthentication no + + # Kerberos options + #KerberosAuthentication no +@@ -81,16 +84,16 @@ AuthorizedKeysFile .ssh/authorized_keys + # If you just want the PAM account and session checks to run without + # PAM authentication, then enable this but set PasswordAuthentication + # and KbdInteractiveAuthentication to 'no'. +-#UsePAM no ++UsePAM yes + + #AllowAgentForwarding yes + #AllowTcpForwarding yes + #GatewayPorts no +-#X11Forwarding no ++X11Forwarding yes + #X11DisplayOffset 10 + #X11UseLocalhost yes + #PermitTTY yes +-#PrintMotd yes ++PrintMotd no + #PrintLastLog yes + #TCPKeepAlive yes + #PermitUserEnvironment no +@@ -107,8 +110,11 @@ AuthorizedKeysFile .ssh/authorized_keys + # no default banner path + #Banner none + ++# Allow client to pass locale environment variables ++AcceptEnv LANG LC_* ++ + # override default of no subsystems +-Subsystem sftp /usr/libexec/sftp-server ++Subsystem sftp /usr/lib/openssh/sftp-server + + # Example of overriding settings on a per-user basis + #Match User anoncvs +diff --git a/sshd_config.5 b/sshd_config.5 +index b9a19c7bd..b13f7665b 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -56,6 +56,35 @@ Arguments may optionally be enclosed in double quotes + .Pq \&" + in order to represent arguments containing spaces. + .Pp ++Note that the Debian ++.Ic openssh-server ++package sets several options as standard in ++.Pa /etc/ssh/sshd_config ++which are not the default in ++.Xr sshd 8 : ++.Pp ++.Bl -bullet -offset indent -compact ++.It ++.Cm Include /etc/ssh/sshd_config.d/*.conf ++.It ++.Cm KbdInteractiveAuthentication No no ++.It ++.Cm X11Forwarding No yes ++.It ++.Cm PrintMotd No no ++.It ++.Cm AcceptEnv No LANG LC_* ++.It ++.Cm Subsystem No sftp /usr/lib/openssh/sftp-server ++.It ++.Cm UsePAM No yes ++.El ++.Pp ++.Pa /etc/ssh/sshd_config.d/*.conf ++files are included at the start of the configuration file, so options set ++there will override those in ++.Pa /etc/ssh/sshd_config. ++.Pp + The possible + keywords and their meanings are as follows (note that + keywords are case-insensitive and arguments are case-sensitive): diff --git a/debian/patches/dnssec-sshfp.patch b/debian/patches/dnssec-sshfp.patch new file mode 100644 index 0000000..ec30655 --- /dev/null +++ b/debian/patches/dnssec-sshfp.patch @@ -0,0 +1,94 @@ +From 25f54fd79c7dc62d5ffaa7ebdc2e3de86a031084 Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Sun, 9 Feb 2014 16:10:01 +0000 +Subject: Force use of DNSSEC even if "options edns0" isn't in resolv.conf + +This allows SSHFP DNS records to be verified if glibc 2.11 is installed. + +Origin: vendor, https://cvs.fedoraproject.org/viewvc/F-12/openssh/openssh-5.2p1-edns.patch?revision=1.1&view=markup +Bug: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=572049 +Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=572049 +Last-Update: 2010-04-06 + +Patch-Name: dnssec-sshfp.patch +--- + dns.c | 14 +++++++++++++- + openbsd-compat/getrrsetbyname.c | 10 +++++----- + openbsd-compat/getrrsetbyname.h | 3 +++ + 3 files changed, 21 insertions(+), 6 deletions(-) + +diff --git a/dns.c b/dns.c +index f2310bec2..9021d1785 100644 +--- a/dns.c ++++ b/dns.c +@@ -198,6 +198,7 @@ verify_host_key_dns(const char *hostname, struct sockaddr *address, + { + u_int counter; + int result; ++ unsigned int rrset_flags = 0; + struct rrsetinfo *fingerprints = NULL; + + u_int8_t hostkey_algorithm; +@@ -220,8 +221,19 @@ verify_host_key_dns(const char *hostname, struct sockaddr *address, + return -1; + } + ++ /* ++ * Original getrrsetbyname function, found on OpenBSD for example, ++ * doesn't accept any flag and prerequisite for obtaining AD bit in ++ * DNS response is set by "options edns0" in resolv.conf. ++ * ++ * Our version is more clever and use RRSET_FORCE_EDNS0 flag. ++ */ ++#ifndef HAVE_GETRRSETBYNAME ++ rrset_flags |= RRSET_FORCE_EDNS0; ++#endif + result = getrrsetbyname(hostname, DNS_RDATACLASS_IN, +- DNS_RDATATYPE_SSHFP, 0, &fingerprints); ++ DNS_RDATATYPE_SSHFP, rrset_flags, &fingerprints); ++ + if (result) { + verbose("DNS lookup error: %s", dns_result_totext(result)); + return -1; +diff --git a/openbsd-compat/getrrsetbyname.c b/openbsd-compat/getrrsetbyname.c +index 73de5e948..4dfa351c6 100644 +--- a/openbsd-compat/getrrsetbyname.c ++++ b/openbsd-compat/getrrsetbyname.c +@@ -214,8 +214,8 @@ getrrsetbyname(const char *hostname, unsigned int rdclass, + goto fail; + } + +- /* don't allow flags yet, unimplemented */ +- if (flags) { ++ /* Allow RRSET_FORCE_EDNS0 flag only. */ ++ if ((flags & !RRSET_FORCE_EDNS0) != 0) { + result = ERRSET_INVAL; + goto fail; + } +@@ -231,9 +231,9 @@ getrrsetbyname(const char *hostname, unsigned int rdclass, + #endif /* DEBUG */ + + #ifdef RES_USE_DNSSEC +- /* turn on DNSSEC if EDNS0 is configured */ +- if (_resp->options & RES_USE_EDNS0) +- _resp->options |= RES_USE_DNSSEC; ++ /* turn on DNSSEC if required */ ++ if (flags & RRSET_FORCE_EDNS0) ++ _resp->options |= (RES_USE_EDNS0|RES_USE_DNSSEC); + #endif /* RES_USE_DNSEC */ + + /* make query */ +diff --git a/openbsd-compat/getrrsetbyname.h b/openbsd-compat/getrrsetbyname.h +index 1283f5506..dbbc85a2a 100644 +--- a/openbsd-compat/getrrsetbyname.h ++++ b/openbsd-compat/getrrsetbyname.h +@@ -72,6 +72,9 @@ + #ifndef RRSET_VALIDATED + # define RRSET_VALIDATED 1 + #endif ++#ifndef RRSET_FORCE_EDNS0 ++# define RRSET_FORCE_EDNS0 0x0001 ++#endif + + /* + * Return codes for getrrsetbyname() diff --git a/debian/patches/doc-hash-tab-completion.patch b/debian/patches/doc-hash-tab-completion.patch new file mode 100644 index 0000000..48d44e5 --- /dev/null +++ b/debian/patches/doc-hash-tab-completion.patch @@ -0,0 +1,28 @@ +From 4202164dacce1c368f7e6e5c02b3080486deddbf Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Sun, 9 Feb 2014 16:10:11 +0000 +Subject: Document that HashKnownHosts may break tab-completion + +Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1727 +Bug-Debian: http://bugs.debian.org/430154 +Last-Update: 2021-11-05 + +Patch-Name: doc-hash-tab-completion.patch +--- + ssh_config.5 | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/ssh_config.5 b/ssh_config.5 +index 92600ed6e..82d68650e 100644 +--- a/ssh_config.5 ++++ b/ssh_config.5 +@@ -921,6 +921,9 @@ Note that existing names and addresses in known hosts files + will not be converted automatically, + but may be manually hashed using + .Xr ssh-keygen 1 . ++Use of this option may break facilities such as tab-completion that rely ++on being able to read unhashed host names from ++.Pa ~/.ssh/known_hosts . + .It Cm HostbasedAcceptedAlgorithms + Specifies the signature algorithms that will be used for hostbased + authentication as a comma-separated list of patterns. diff --git a/debian/patches/gnome-ssh-askpass2-icon.patch b/debian/patches/gnome-ssh-askpass2-icon.patch new file mode 100644 index 0000000..50f27b4 --- /dev/null +++ b/debian/patches/gnome-ssh-askpass2-icon.patch @@ -0,0 +1,26 @@ +From 0b0ba78b1a3a0a7fd2d0d72f508d225c04df5aa7 Mon Sep 17 00:00:00 2001 +From: Vincent Untz <vuntz@ubuntu.com> +Date: Sun, 9 Feb 2014 16:10:16 +0000 +Subject: Give the ssh-askpass-gnome window a default icon + +Bug-Ubuntu: https://bugs.launchpad.net/bugs/27152 +Last-Update: 2010-02-28 + +Patch-Name: gnome-ssh-askpass2-icon.patch +--- + contrib/gnome-ssh-askpass2.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/contrib/gnome-ssh-askpass2.c b/contrib/gnome-ssh-askpass2.c +index a62f98152..304569802 100644 +--- a/contrib/gnome-ssh-askpass2.c ++++ b/contrib/gnome-ssh-askpass2.c +@@ -320,6 +320,8 @@ main(int argc, char **argv) + + gtk_init(&argc, &argv); + ++ gtk_window_set_default_icon_from_file ("/usr/share/pixmaps/ssh-askpass-gnome.png", NULL); ++ + if (argc > 1) { + message = g_strjoinv(" ", argv + 1); + } else { diff --git a/debian/patches/gssapi.patch b/debian/patches/gssapi.patch new file mode 100644 index 0000000..072c079 --- /dev/null +++ b/debian/patches/gssapi.patch @@ -0,0 +1,4156 @@ +From 61798b25a23b55d72a86a35062106cc3fc0ab834 Mon Sep 17 00:00:00 2001 +From: Simon Wilkinson <simon@sxw.org.uk> +Date: Sun, 9 Feb 2014 16:09:48 +0000 +Subject: GSSAPI key exchange support + +This patch has been rejected upstream: "None of the OpenSSH developers are +in favour of adding this, and this situation has not changed for several +years. This is not a slight on Simon's patch, which is of fine quality, but +just that a) we don't trust GSSAPI implementations that much and b) we don't +like adding new KEX since they are pre-auth attack surface. This one is +particularly scary, since it requires hooks out to typically root-owned +system resources." + +However, quite a lot of people rely on this in Debian, and it's better to +have it merged into the main openssh package rather than having separate +-krb5 packages (as we used to have). It seems to have a generally good +security history. + +Author: Simon Wilkinson <simon@sxw.org.uk> +Author: Colin Watson <cjwatson@debian.org> +Author: Jakub Jelen <jjelen@redhat.com> +Origin: other, https://github.com/openssh-gsskex/openssh-gsskex/pull/23 +Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1242 +Last-Updated: 2023-01-02 + +Patch-Name: gssapi.patch +--- + Makefile.in | 5 +- + README.md | 36 +++ + auth.c | 94 +------- + auth2-gss.c | 56 ++++- + auth2.c | 2 + + canohost.c | 91 ++++++++ + canohost.h | 3 + + clientloop.c | 13 ++ + configure.ac | 24 ++ + gss-genr.c | 297 +++++++++++++++++++++++- + gss-serv-krb5.c | 87 ++++++- + gss-serv.c | 205 +++++++++++++++-- + kex.c | 66 +++++- + kex.h | 29 +++ + kexdh.c | 10 + + kexgen.c | 2 +- + kexgssc.c | 599 ++++++++++++++++++++++++++++++++++++++++++++++++ + kexgsss.c | 474 ++++++++++++++++++++++++++++++++++++++ + monitor.c | 139 ++++++++++- + monitor.h | 2 + + monitor_wrap.c | 57 ++++- + monitor_wrap.h | 4 +- + readconf.c | 74 ++++++ + readconf.h | 6 + + servconf.c | 47 ++++ + servconf.h | 3 + + session.c | 10 +- + ssh-gss.h | 54 ++++- + ssh-null.c | 108 +++++++++ + ssh.1 | 8 + + ssh.c | 6 +- + ssh_config | 2 + + ssh_config.5 | 57 +++++ + sshconnect2.c | 156 ++++++++++++- + sshd.c | 62 ++++- + sshd_config | 2 + + sshd_config.5 | 30 +++ + sshkey.c | 8 +- + sshkey.h | 1 + + 39 files changed, 2765 insertions(+), 164 deletions(-) + create mode 100644 kexgssc.c + create mode 100644 kexgsss.c + create mode 100644 ssh-null.c + +diff --git a/Makefile.in b/Makefile.in +index c0ebfa041..32b607719 100644 +--- a/Makefile.in ++++ b/Makefile.in +@@ -101,7 +101,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ + readpass.o ttymodes.o xmalloc.o addr.o addrmatch.o \ + atomicio.o dispatch.o mac.o misc.o utf8.o \ + monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-ecdsa-sk.o \ +- ssh-ed25519-sk.o ssh-rsa.o dh.o \ ++ ssh-ed25519-sk.o ssh-rsa.o ssh-null.o dh.o \ + msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \ + ssh-pkcs11.o smult_curve25519_ref.o \ + poly1305.o chacha.o cipher-chachapoly.o cipher-chachapoly-libcrypto.o \ +@@ -110,6 +110,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ + kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ + kexgexc.o kexgexs.o \ + kexsntrup761x25519.o sntrup761.o kexgen.o \ ++ kexgssc.o \ + sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ + sshbuf-io.o + +@@ -126,7 +127,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.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 \ +- auth2-gss.o gss-serv.o gss-serv-krb5.o \ ++ auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o \ + loginrec.o auth-pam.o auth-shadow.o auth-sia.o \ + srclimit.o sftp-server.o sftp-common.o \ + sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \ +diff --git a/README.md b/README.md +index 3da933817..a338802ff 100644 +--- a/README.md ++++ b/README.md +@@ -1,3 +1,39 @@ ++Portable OpenSSH with GSSAPI Key Exchange patches ++================================================= ++ ++[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/openssh-gsskex/openssh-gsskex.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/openssh-gsskex/openssh-gsskex/context:cpp) ++ ++Currently, there are two branches with gssapi key exchange related ++patches: ++ ++ * fedora/master: Changes that are shipped in Fedora [![Build Status](https://travis-ci.org/openssh-gsskex/openssh-gsskex.svg?branch=fedora%2Fmaster)](https://travis-ci.org/openssh-gsskex/openssh-gsskex) ++ * debian/master: Changes that are shipped in Debian [![Build Status](https://travis-ci.org/openssh-gsskex/openssh-gsskex.svg?branch=debian%2Fmaster)](https://travis-ci.org/openssh-gsskex/openssh-gsskex) ++ ++The target is to converge to a shared repository with single master ++branch from where we could build releases for both OSes. ++ ++ ++What is in: ++ ++ * The original patch implementing missing parts of RFC4462 by Simon Wilkinson ++ adapted to the current OpenSSH versions and with several fixes ++ * New methods for GSSAPI Kex from IETF draft [1] from Jakub Jelen ++ ++ ++Missing kerberos-related parts: ++ ++ * .k5login and .kusers support available in Fedora [2] [3]. ++ * Improved handling of kerberos ccache location [4] ++ ++ ++ ++[1] https://tools.ietf.org/html/draft-ietf-curdle-gss-keyex-sha2-08 ++[2] https://src.fedoraproject.org/rpms/openssh/blob/master/f/openssh-6.6p1-kuserok.patch ++[3] https://src.fedoraproject.org/rpms/openssh/blob/master/f/openssh-6.6p1-GSSAPIEnablek5users.patch ++[4] https://bugzilla.mindrot.org/show_bug.cgi?id=2775 ++ ++------------------------------------------------------------------------------- ++ + # Portable OpenSSH + + [![C/C++ CI](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml) +diff --git a/auth.c b/auth.c +index 03a777cce..946dc6074 100644 +--- a/auth.c ++++ b/auth.c +@@ -358,7 +358,8 @@ auth_root_allowed(struct ssh *ssh, const char *method) + 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: +@@ -638,97 +639,6 @@ fakepw(void) + return (&fake); + } + +-/* +- * 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 based on conflation of hostnames and IP addresses. +- */ +- +-static 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); +-} +- + /* + * Return the canonical name of the host in the other side of the current + * connection. The host name is cached, so it is efficient to call this +diff --git a/auth2-gss.c b/auth2-gss.c +index 2062609d9..4566d425c 100644 +--- a/auth2-gss.c ++++ b/auth2-gss.c +@@ -1,7 +1,7 @@ + /* $OpenBSD: auth2-gss.c,v 1.33 2021/12/19 22:12:07 djm 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 +@@ -55,6 +55,48 @@ static int input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh); + static int input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh); + static int input_gssapi_errtok(int, u_int32_t, struct ssh *); + ++/* ++ * The 'gssapi_keyex' userauth mechanism. ++ */ ++static int ++userauth_gsskeyex(struct ssh *ssh) ++{ ++ 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(PRIVSEP(ssh_gssapi_checkmic(gss_kex_context, ++ &gssbuf, &mic)))) ++ authenticated = PRIVSEP(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) +@@ -261,7 +303,8 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh) + if ((r = sshpkt_get_end(ssh)) != 0) + fatal_fr(r, "parse packet"); + +- authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); ++ authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user, ++ authctxt->pw, 1)); + + if ((!use_privsep || mm_is_monitor()) && + (displayname = ssh_gssapi_displayname()) != NULL) +@@ -307,7 +350,8 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh) + gssbuf.length = sshbuf_len(b); + + if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic)))) +- authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); ++ authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user, ++ authctxt->pw, 0)); + else + logit("GSSAPI MIC check failed"); + +@@ -327,6 +371,12 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh) + return 0; + } + ++Authmethod method_gsskeyex = { ++ "gssapi-keyex", ++ userauth_gsskeyex, ++ &options.gss_authentication ++}; ++ + Authmethod method_gssapi = { + "gssapi-with-mic", + NULL, +diff --git a/auth2.c b/auth2.c +index 6c061934b..44bd49177 100644 +--- a/auth2.c ++++ b/auth2.c +@@ -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 --git a/canohost.c b/canohost.c +index a810da0ee..99f1cd406 100644 +--- a/canohost.c ++++ b/canohost.c +@@ -35,6 +35,97 @@ + #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. ++ */ ++ ++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 --git a/canohost.h b/canohost.h +index 26d62855a..0cadc9f18 100644 +--- a/canohost.h ++++ b/canohost.h +@@ -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 --git a/clientloop.c b/clientloop.c +index fef9efc6c..8f1fac58a 100644 +--- a/clientloop.c ++++ b/clientloop.c +@@ -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" + +@@ -1415,6 +1419,15 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, + /* Do channel operations. */ + channel_after_poll(ssh, pfd, npfd_active); + ++#ifdef GSSAPI ++ if (!ssh_packet_is_rekeying(ssh) && ++ 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 --git a/configure.ac b/configure.ac +index 22fee70f6..75a9e5670 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -756,6 +756,30 @@ int main(void) { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16)) + [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 <Security/AuthSession.h>], ++ [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 <Kerberos/Kerberos.h>], ++ [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 --git a/gss-genr.c b/gss-genr.c +index 2cd695e54..9f9745b7f 100644 +--- a/gss-genr.c ++++ b/gss-genr.c +@@ -1,7 +1,7 @@ + /* $OpenBSD: gss-genr.c,v 1.28 2021/01/27 10:05:28 djm Exp $ */ + + /* +- * Copyright (c) 2001-2007 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 +@@ -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 *b, gss_buffer_desc *g) + 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) +@@ -216,7 +393,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok, + } + + 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); + +@@ -245,9 +422,43 @@ ssh_gssapi_import_name(Gssctxt *ctx, const char *host) + return (ctx->major); + } + ++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 +466,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) + 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 +495,16 @@ ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service, + } + + 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 +514,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) + 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 +527,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) + 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 --git a/gss-serv-krb5.c b/gss-serv-krb5.c +index a151bc1e4..ef20401ec 100644 +--- a/gss-serv-krb5.c ++++ b/gss-serv-krb5.c +@@ -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_client *client) + krb5_error_code problem; + krb5_principal princ; + OM_uint32 maj_status, min_status; +- int len; ++ const char *new_ccname; + const char *errmsg; + + if (client->creds == NULL) { +@@ -180,11 +180,16 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) + return; + } + +- client->store.filename = xstrdup(krb5_cc_get_name(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 ++ xasprintf(&client->store.envval, "FILE:%s", new_ccname); ++ client->store.filename = xstrdup(new_ccname); ++#endif + + #ifdef USE_PAM + if (options.use_pam) +@@ -193,9 +198,76 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) + + 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 +275,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 --git a/gss-serv.c b/gss-serv.c +index b5d4bb2d1..4287579ae 100644 +--- a/gss-serv.c ++++ b/gss-serv.c +@@ -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; +@@ -140,6 +142,29 @@ ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid) + return (ssh_gssapi_acquire_cred(*ctx)); + } + ++/* 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(PRIVSEP(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 *oidset) + 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; ++ } ++ ++ 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); ++ } + +- gss_buffer_desc ename; ++ 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_gssapi_client *client) + 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_gssapi_client *client) + 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_gssapi_client *client) + 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 *envsizep) + + /* 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,90 @@ 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 = PRIVSEP(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 ++ if (!use_privsep) { ++ debug("Not even going to try and do PAM with privsep disabled"); ++ return; ++ } ++ ++ 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 --git a/kex.c b/kex.c +index 8cdefcf7c..c259e00fc 100644 +--- a/kex.c ++++ b/kex.c +@@ -57,11 +57,16 @@ + #include "misc.h" + #include "dispatch.h" + #include "monitor.h" ++#include "xmalloc.h" + + #include "ssherr.h" + #include "sshbuf.h" + #include "digest.h" + ++#ifdef GSSAPI ++#include "ssh-gss.h" ++#endif ++ + /* prototype */ + static int kex_choose_conf(struct ssh *); + static int kex_input_newkeys(int, u_int32_t, struct ssh *); +@@ -117,15 +122,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); +@@ -140,6 +158,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) + { +@@ -149,6 +179,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; + } + +@@ -317,6 +351,29 @@ kex_assemble_names(char **listp, const char *def, const char *all) + 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; ++} ++ + /* put algorithm proposal into buffer */ + int + kex_prop2buf(struct sshbuf *b, char *proposal[PROPOSAL_MAX]) +@@ -719,6 +776,9 @@ kex_free(struct kex *kex) + sshbuf_free(kex->session_id); + sshbuf_free(kex->initial_sig); + sshkey_free(kex->initial_hostkey); ++#ifdef GSSAPI ++ free(kex->gss_host); ++#endif /* GSSAPI */ + free(kex->failed_choice); + free(kex->hostkey_alg); + free(kex->name); +diff --git a/kex.h b/kex.h +index c35329501..240dca89d 100644 +--- a/kex.h ++++ b/kex.h +@@ -102,6 +102,15 @@ enum kex_exchange { + KEX_ECDH_SHA2, + KEX_C25519_SHA256, + KEX_KEM_SNTRUP761X25519_SHA512, ++#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 + }; + +@@ -159,6 +168,12 @@ struct kex { + u_int flags; + int hash_alg; + int ec_nid; ++#ifdef GSSAPI ++ 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 *); +@@ -180,8 +195,10 @@ struct kex { + + 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_assemble_names(char **, const char *, const char *); ++int kex_gss_names_valid(const char *); + + int kex_exchange_identification(struct ssh *, int, const char *); + +@@ -209,6 +226,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 **, +@@ -241,6 +264,12 @@ int kexgex_hash(int, const struct sshbuf *, const struct sshbuf *, + 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 --git a/kexdh.c b/kexdh.c +index c1084f214..0faab21b0 100644 +--- a/kexdh.c ++++ b/kexdh.c +@@ -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 --git a/kexgen.c b/kexgen.c +index 20f3c5711..ca704844e 100644 +--- a/kexgen.c ++++ b/kexgen.c +@@ -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 --git a/kexgssc.c b/kexgssc.c +new file mode 100644 +index 000000000..1c62740e7 +--- /dev/null ++++ b/kexgssc.c +@@ -0,0 +1,599 @@ ++/* ++ * 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 <openssl/crypto.h> ++#include <openssl/bn.h> ++ ++#include <string.h> ++ ++#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" ++ ++int ++kexgss_client(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER, ++ recv_tok = GSS_C_EMPTY_BUFFER, ++ gssbuf, msg_tok = GSS_C_EMPTY_BUFFER, *token_ptr; ++ Gssctxt *ctxt; ++ OM_uint32 maj_status, min_status, ret_flags; ++ struct sshbuf *server_blob = NULL; ++ struct sshbuf *shared_secret = NULL; ++ struct sshbuf *server_host_key_blob = NULL; ++ struct sshbuf *empty = NULL; ++ u_char *msg; ++ int type = 0; ++ int first = 1; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ u_char c; ++ int r; ++ ++ /* Initialise our GSSAPI world */ ++ ssh_gssapi_build_ctx(&ctxt); ++ if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type) ++ == GSS_C_NO_OID) ++ fatal("Couldn't identify host exchange"); ++ ++ if (ssh_gssapi_import_name(ctxt, kex->gss_host)) ++ fatal("Couldn't import hostname"); ++ ++ if (kex->gss_client && ++ ssh_gssapi_client_identity(ctxt, 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) ++ return r; ++ ++ token_ptr = GSS_C_NO_BUFFER; ++ ++ do { ++ debug("Calling gss_init_sec_context"); ++ ++ maj_status = ssh_gssapi_init_ctx(ctxt, ++ kex->gss_deleg_creds, token_ptr, &send_tok, ++ &ret_flags); ++ ++ if (GSS_ERROR(maj_status)) { ++ /* XXX Useles 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(&min_status, &recv_tok); ++ ++ if (maj_status == 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 (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)); ++ 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(&min_status, &send_tok); ++ ++ /* If we've sent them data, they should reply */ ++ do { ++ type = ssh_packet_read(ssh); ++ if (type == SSH2_MSG_KEXGSS_HOSTKEY) { ++ debug("Received KEXGSS_HOSTKEY"); ++ if (server_host_key_blob) ++ fatal("Server host key received more than once"); ++ if ((r = sshpkt_getb_froms(ssh, &server_host_key_blob)) != 0) ++ fatal("Failed to read server host key: %s", ssh_err(r)); ++ } ++ } while (type == SSH2_MSG_KEXGSS_HOSTKEY); ++ ++ switch (type) { ++ case SSH2_MSG_KEXGSS_CONTINUE: ++ debug("Received GSSAPI_CONTINUE"); ++ if (maj_status == 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)); ++ break; ++ case SSH2_MSG_KEXGSS_COMPLETE: ++ debug("Received GSSAPI_COMPLETE"); ++ if (msg_tok.value != NULL) ++ fatal("Received GSSAPI_COMPLETE twice?"); ++ if ((r = sshpkt_getb_froms(ssh, &server_blob)) != 0 || ++ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, ++ &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 (maj_status == GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: received token when complete"); ++ } else { ++ /* No token included */ ++ if (maj_status != 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."); ++ } ++ break; ++ case SSH2_MSG_KEXGSS_ERROR: ++ debug("Received Error"); ++ if ((r = sshpkt_get_u32(ssh, &maj_status)) != 0 || ++ (r = sshpkt_get_u32(ssh, &min_status)) != 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); ++ default: ++ sshpkt_disconnect(ssh, "Protocol error: didn't expect packet type %d", ++ type); ++ } ++ token_ptr = &recv_tok; ++ } else { ++ /* No data, and not complete */ ++ if (maj_status != GSS_S_COMPLETE) ++ fatal("Not complete, and no token output"); ++ } ++ } while (maj_status & GSS_S_CONTINUE_NEEDED); ++ ++ /* ++ * We _must_ have received a COMPLETE message in reply from the ++ * server, which will have set server_blob and msg_tok ++ */ ++ ++ if (type != SSH2_MSG_KEXGSS_COMPLETE) ++ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); ++ ++ /* 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, server_blob, &shared_secret); ++ break; ++ case KEX_GSS_C25519_SHA256: ++ if (sshbuf_ptr(server_blob)[sshbuf_len(server_blob)] & 0x80) ++ fatal("The received key has MSB of last octet set!"); ++ r = kex_c25519_dec(kex, server_blob, &shared_secret); ++ break; ++ case KEX_GSS_NISTP256_SHA256: ++ if (sshbuf_len(server_blob) != 65) ++ fatal("The received NIST-P256 key did not match" ++ "expected length (expected 65, got %zu)", sshbuf_len(server_blob)); ++ ++ if (sshbuf_ptr(server_blob)[0] != POINT_CONVERSION_UNCOMPRESSED) ++ fatal("The received NIST-P256 key does not have first octet 0x04"); ++ ++ r = kex_ecdh_dec(kex, server_blob, &shared_secret); ++ break; ++ default: ++ r = SSH_ERR_INVALID_ARGUMENT; ++ break; ++ } ++ if (r != 0) ++ goto out; ++ ++ if ((empty = sshbuf_new()) == NULL) { ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ hashlen = sizeof(hash); ++ if ((r = kex_gen_hash( ++ kex->hash_alg, ++ kex->client_version, ++ kex->server_version, ++ kex->my, ++ kex->peer, ++ (server_host_key_blob ? server_host_key_blob : empty), ++ kex->client_pub, ++ server_blob, ++ shared_secret, ++ hash, &hashlen)) != 0) ++ fatal_f("Unexpected KEX type %d", kex->kex_type); ++ ++ gssbuf.value = hash; ++ gssbuf.length = hashlen; ++ ++ /* Verify that the hash matches the MIC we just got. */ ++ if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) ++ sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); ++ ++ gss_release_buffer(&min_status, &msg_tok); ++ ++ if (kex->gss_deleg_creds) ++ ssh_gssapi_credentials_updated(ctxt); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = ctxt; ++ else ++ ssh_gssapi_delete_ctx(&ctxt); ++ ++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) ++ r = kex_send_newkeys(ssh); ++ ++out: ++ explicit_bzero(hash, sizeof(hash)); ++ explicit_bzero(kex->c25519_client_key, sizeof(kex->c25519_client_key)); ++ sshbuf_free(empty); ++ sshbuf_free(server_host_key_blob); ++ sshbuf_free(server_blob); ++ sshbuf_free(shared_secret); ++ sshbuf_free(kex->client_pub); ++ kex->client_pub = NULL; ++ return r; ++} ++ ++int ++kexgssgex_client(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER, ++ recv_tok = GSS_C_EMPTY_BUFFER, gssbuf, ++ msg_tok = GSS_C_EMPTY_BUFFER, *token_ptr; ++ Gssctxt *ctxt; ++ OM_uint32 maj_status, min_status, ret_flags; ++ struct sshbuf *shared_secret = NULL; ++ BIGNUM *p = NULL; ++ BIGNUM *g = NULL; ++ struct sshbuf *buf = NULL; ++ struct sshbuf *server_host_key_blob = NULL; ++ struct sshbuf *server_blob = NULL; ++ BIGNUM *dh_server_pub = NULL; ++ u_char *msg; ++ int type = 0; ++ int first = 1; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ const BIGNUM *pub_key, *dh_p, *dh_g; ++ int nbits = 0, min = DH_GRP_MIN, max = DH_GRP_MAX; ++ struct sshbuf *empty = NULL; ++ u_char c; ++ int r; ++ ++ /* Initialise our GSSAPI world */ ++ ssh_gssapi_build_ctx(&ctxt); ++ if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type) ++ == GSS_C_NO_OID) ++ fatal("Couldn't identify host exchange"); ++ ++ if (ssh_gssapi_import_name(ctxt, kex->gss_host)) ++ fatal("Couldn't import hostname"); ++ ++ if (kex->gss_client && ++ ssh_gssapi_client_identity(ctxt, kex->gss_client)) ++ fatal("Couldn't acquire client credentials"); ++ ++ debug("Doing group exchange"); ++ nbits = dh_estimate(kex->dh_need * 8); ++ ++ kex->min = DH_GRP_MIN; ++ kex->max = DH_GRP_MAX; ++ kex->nbits = nbits; ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUPREQ)) != 0 || ++ (r = sshpkt_put_u32(ssh, min)) != 0 || ++ (r = sshpkt_put_u32(ssh, nbits)) != 0 || ++ (r = sshpkt_put_u32(ssh, max)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("Failed to construct a packet: %s", ssh_err(r)); ++ ++ if ((r = ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0) ++ fatal("Error: %s", ssh_err(r)); ++ ++ 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) < min || BN_num_bits(p) > max) ++ fatal("GSSGRP_GEX group out of range: %d !< %d !< %d", ++ min, BN_num_bits(p), 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) ++ goto out; ++ DH_get0_key(kex->dh, &pub_key, NULL); ++ ++ token_ptr = GSS_C_NO_BUFFER; ++ ++ do { ++ /* Step 2 - call GSS_Init_sec_context() */ ++ debug("Calling gss_init_sec_context"); ++ ++ maj_status = ssh_gssapi_init_ctx(ctxt, ++ kex->gss_deleg_creds, token_ptr, &send_tok, ++ &ret_flags); ++ ++ if (GSS_ERROR(maj_status)) { ++ /* XXX Useles 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(&min_status, &recv_tok); ++ ++ if (maj_status == 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 (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_bignum2(ssh, pub_key)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ 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("sshpkt failed: %s", ssh_err(r)); ++ } ++ if ((r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt_send failed: %s", ssh_err(r)); ++ gss_release_buffer(&min_status, &send_tok); ++ ++ /* If we've sent them data, they should reply */ ++ do { ++ type = ssh_packet_read(ssh); ++ if (type == SSH2_MSG_KEXGSS_HOSTKEY) { ++ debug("Received KEXGSS_HOSTKEY"); ++ if (server_host_key_blob) ++ fatal("Server host key received more than once"); ++ if ((r = sshpkt_getb_froms(ssh, &server_host_key_blob)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ } while (type == SSH2_MSG_KEXGSS_HOSTKEY); ++ ++ switch (type) { ++ case SSH2_MSG_KEXGSS_CONTINUE: ++ debug("Received GSSAPI_CONTINUE"); ++ if (maj_status == 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("sshpkt failed: %s", ssh_err(r)); ++ break; ++ case SSH2_MSG_KEXGSS_COMPLETE: ++ debug("Received GSSAPI_COMPLETE"); ++ if (msg_tok.value != NULL) ++ fatal("Received GSSAPI_COMPLETE twice?"); ++ if ((r = sshpkt_getb_froms(ssh, &server_blob)) != 0 || ++ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, ++ &msg_tok)) != 0) ++ fatal("sshpkt failed: %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 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ /* If we're already complete - protocol error */ ++ if (maj_status == GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: received token when complete"); ++ } else { ++ /* No token included */ ++ if (maj_status != GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); ++ } ++ break; ++ case SSH2_MSG_KEXGSS_ERROR: ++ debug("Received Error"); ++ if ((r = sshpkt_get_u32(ssh, &maj_status)) != 0 || ++ (r = sshpkt_get_u32(ssh, &min_status)) != 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 failed: %s", ssh_err(r)); ++ fatal("GSSAPI Error: \n%.400s", msg); ++ default: ++ sshpkt_disconnect(ssh, "Protocol error: didn't expect packet type %d", ++ type); ++ } ++ token_ptr = &recv_tok; ++ } else { ++ /* No data, and not complete */ ++ if (maj_status != GSS_S_COMPLETE) ++ fatal("Not complete, and no token output"); ++ } ++ } while (maj_status & GSS_S_CONTINUE_NEEDED); ++ ++ /* ++ * We _must_ have received a COMPLETE message in reply from the ++ * server, which will have set dh_server_pub and msg_tok ++ */ ++ ++ if (type != SSH2_MSG_KEXGSS_COMPLETE) ++ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); ++ ++ /* 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, server_blob)) != 0 || ++ (r = sshbuf_get_bignum2(buf, &dh_server_pub)) != 0) ++ goto out; ++ sshbuf_free(buf); ++ buf = NULL; ++ ++ if ((shared_secret = sshbuf_new()) == NULL) { ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0) ++ goto out; ++ if ((empty = sshbuf_new()) == NULL) { ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); ++ hashlen = sizeof(hash); ++ if ((r = kexgex_hash( ++ kex->hash_alg, ++ kex->client_version, ++ kex->server_version, ++ kex->my, ++ kex->peer, ++ (server_host_key_blob ? 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)) != 0) ++ fatal("Failed to calculate hash: %s", ssh_err(r)); ++ ++ gssbuf.value = hash; ++ gssbuf.length = hashlen; ++ ++ /* Verify that the hash matches the MIC we just got. */ ++ if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) ++ sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); ++ ++ gss_release_buffer(&min_status, &msg_tok); ++ ++ if (kex->gss_deleg_creds) ++ ssh_gssapi_credentials_updated(ctxt); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = ctxt; ++ else ++ ssh_gssapi_delete_ctx(&ctxt); ++ ++ /* Finally derive the keys and send them */ ++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) ++ r = kex_send_newkeys(ssh); ++out: ++ sshbuf_free(buf); ++ sshbuf_free(server_blob); ++ sshbuf_free(empty); ++ explicit_bzero(hash, sizeof(hash)); ++ DH_free(kex->dh); ++ kex->dh = NULL; ++ BN_clear_free(dh_server_pub); ++ sshbuf_free(shared_secret); ++ sshbuf_free(server_host_key_blob); ++ return r; ++} ++#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ +diff --git a/kexgsss.c b/kexgsss.c +new file mode 100644 +index 000000000..a2c02148b +--- /dev/null ++++ b/kexgsss.c +@@ -0,0 +1,474 @@ ++/* ++ * 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 <string.h> ++ ++#include <openssl/crypto.h> ++#include <openssl/bn.h> ++ ++#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; ++ ++int ++kexgss_server(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ OM_uint32 maj_status, min_status; ++ ++ /* ++ * Some GSSAPI implementations use the input value of ret_flags (an ++ * output variable) as a means of triggering mechanism specific ++ * features. Initializing it to zero avoids inadvertently ++ * activating this non-standard behaviour. ++ */ ++ ++ OM_uint32 ret_flags = 0; ++ gss_buffer_desc gssbuf, recv_tok, msg_tok; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; ++ Gssctxt *ctxt = NULL; ++ struct sshbuf *shared_secret = NULL; ++ struct sshbuf *client_pubkey = NULL; ++ struct sshbuf *server_pubkey = NULL; ++ struct sshbuf *empty = sshbuf_new(); ++ int type = 0; ++ gss_OID oid; ++ char *mechs; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ int r; ++ ++ /* 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(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid)))) ++ fatal("Unable to acquire credentials for the server"); ++ ++ do { ++ debug("Wait SSH2_MSG_KEXGSS_INIT"); ++ type = ssh_packet_read(ssh); ++ switch(type) { ++ case SSH2_MSG_KEXGSS_INIT: ++ if (client_pubkey != NULL) ++ fatal("Received KEXGSS_INIT after initialising"); ++ 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, &server_pubkey, ++ &shared_secret); ++ break; ++ case KEX_GSS_NISTP256_SHA256: ++ r = kex_ecdh_enc(kex, client_pubkey, &server_pubkey, ++ &shared_secret); ++ break; ++ case KEX_GSS_C25519_SHA256: ++ r = kex_c25519_enc(kex, client_pubkey, &server_pubkey, ++ &shared_secret); ++ break; ++ default: ++ fatal_f("Unexpected KEX type %d", kex->kex_type); ++ } ++ if (r != 0) ++ goto out; ++ ++ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ ++ break; ++ case SSH2_MSG_KEXGSS_CONTINUE: ++ 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)); ++ break; ++ default: ++ sshpkt_disconnect(ssh, ++ "Protocol error: didn't expect packet type %d", ++ type); ++ } ++ ++ maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok, ++ &send_tok, &ret_flags)); ++ ++ gss_release_buffer(&min_status, &recv_tok); ++ ++ if (maj_status != GSS_S_COMPLETE && send_tok.length == 0) ++ fatal("Zero length token output when incomplete"); ++ ++ if (client_pubkey == NULL) ++ fatal("No client public key"); ++ ++ if (maj_status & 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(&min_status, &send_tok); ++ } ++ } while (maj_status & GSS_S_CONTINUE_NEEDED); ++ ++ if (GSS_ERROR(maj_status)) { ++ 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"); ++ ++ hashlen = sizeof(hash); ++ if ((r = kex_gen_hash( ++ kex->hash_alg, ++ kex->client_version, ++ kex->server_version, ++ kex->peer, ++ kex->my, ++ empty, ++ client_pubkey, ++ server_pubkey, ++ shared_secret, ++ hash, &hashlen)) != 0) ++ goto out; ++ ++ gssbuf.value = hash; ++ gssbuf.length = hashlen; ++ ++ if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok)))) ++ fatal("Couldn't get MIC"); ++ ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 || ++ (r = sshpkt_put_stringb(ssh, 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(&min_status, &send_tok); ++ gss_release_buffer(&min_status, &msg_tok); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = ctxt; ++ else ++ ssh_gssapi_delete_ctx(&ctxt); ++ ++ 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(); ++out: ++ sshbuf_free(empty); ++ explicit_bzero(hash, sizeof(hash)); ++ sshbuf_free(shared_secret); ++ sshbuf_free(client_pubkey); ++ sshbuf_free(server_pubkey); ++ return r; ++} ++ ++int ++kexgssgex_server(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ OM_uint32 maj_status, min_status; ++ ++ /* ++ * Some GSSAPI implementations use the input value of ret_flags (an ++ * output variable) as a means of triggering mechanism specific ++ * features. Initializing it to zero avoids inadvertently ++ * activating this non-standard behaviour. ++ */ ++ ++ OM_uint32 ret_flags = 0; ++ gss_buffer_desc gssbuf, recv_tok, msg_tok; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; ++ Gssctxt *ctxt = NULL; ++ struct sshbuf *shared_secret = NULL; ++ int type = 0; ++ gss_OID oid; ++ char *mechs; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ BIGNUM *dh_client_pub = NULL; ++ const BIGNUM *pub_key, *dh_p, *dh_g; ++ int min = -1, max = -1, nbits = -1; ++ int cmin = -1, cmax = -1; /* client proposal */ ++ struct sshbuf *empty = sshbuf_new(); ++ int r; ++ ++ /* 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()) ++ if ((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(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid)))) ++ fatal("Unable to acquire credentials for the server"); ++ ++ /* 5. S generates an ephemeral key pair (do the allocations early) */ ++ debug("Doing group exchange"); ++ ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUPREQ); ++ /* 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 = PRIVSEP(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) ++ goto out; ++ ++ do { ++ debug("Wait SSH2_MSG_GSSAPI_INIT"); ++ type = ssh_packet_read(ssh); ++ switch(type) { ++ case SSH2_MSG_KEXGSS_INIT: ++ if (dh_client_pub != NULL) ++ fatal("Received KEXGSS_INIT after initialising"); ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, ++ &recv_tok)) != 0 || ++ (r = sshpkt_get_bignum2(ssh, &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 */ ++ break; ++ case SSH2_MSG_KEXGSS_CONTINUE: ++ 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)); ++ break; ++ default: ++ sshpkt_disconnect(ssh, ++ "Protocol error: didn't expect packet type %d", ++ type); ++ } ++ ++ maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok, ++ &send_tok, &ret_flags)); ++ ++ gss_release_buffer(&min_status, &recv_tok); ++ ++ if (maj_status != GSS_S_COMPLETE && send_tok.length == 0) ++ fatal("Zero length token output when incomplete"); ++ ++ if (dh_client_pub == NULL) ++ fatal("No client public key"); ++ ++ if (maj_status & 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(&min_status, &send_tok); ++ } ++ } while (maj_status & GSS_S_CONTINUE_NEEDED); ++ ++ if (GSS_ERROR(maj_status)) { ++ 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 */ ++ if ((shared_secret = sshbuf_new()) == NULL) { ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ if ((r = kex_dh_compute_key(kex, dh_client_pub, shared_secret)) != 0) ++ goto out; ++ ++ DH_get0_key(kex->dh, &pub_key, NULL); ++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); ++ hashlen = sizeof(hash); ++ if ((r = kexgex_hash( ++ kex->hash_alg, ++ kex->client_version, ++ kex->server_version, ++ kex->peer, ++ kex->my, ++ empty, ++ cmin, nbits, cmax, ++ dh_p, dh_g, ++ dh_client_pub, ++ pub_key, ++ sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), ++ hash, &hashlen)) != 0) ++ fatal("kexgex_hash failed: %s", ssh_err(r)); ++ ++ gssbuf.value = hash; ++ gssbuf.length = hashlen; ++ ++ if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt, &gssbuf, &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 failed: %s", ssh_err(r)); ++ ++ gss_release_buffer(&min_status, &send_tok); ++ gss_release_buffer(&min_status, &msg_tok); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = ctxt; ++ else ++ ssh_gssapi_delete_ctx(&ctxt); ++ ++ /* 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(); ++out: ++ sshbuf_free(empty); ++ explicit_bzero(hash, sizeof(hash)); ++ DH_free(kex->dh); ++ kex->dh = NULL; ++ BN_clear_free(dh_client_pub); ++ sshbuf_free(shared_secret); ++ return r; ++} ++#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ +diff --git a/monitor.c b/monitor.c +index 91e0e6245..5f7df9539 100644 +--- a/monitor.c ++++ b/monitor.c +@@ -145,6 +145,8 @@ int mm_answer_gss_setup_ctx(struct ssh *, int, struct sshbuf *); + 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 +@@ -217,11 +219,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 +@@ -290,6 +299,10 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor) + /* 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) { +@@ -401,6 +414,10 @@ monitor_child_postauth(struct ssh *ssh, struct monitor *pmonitor) + 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); +@@ -1744,6 +1761,17 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor) + # 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; +@@ -1836,8 +1864,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m) + 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"); +@@ -1869,8 +1897,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m) + 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"); +@@ -1890,6 +1918,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m) + 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); + } +@@ -1901,8 +1930,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m) + 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) +@@ -1928,13 +1957,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m) + 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) +@@ -1943,7 +1976,11 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) + 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); +@@ -1951,5 +1988,83 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) + /* Monitor loop will terminate if authenticated */ + return (authenticated); + } +-#endif /* GSSAPI */ + ++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 --git a/monitor.h b/monitor.h +index 683e5e071..2b1a2d590 100644 +--- a/monitor.h ++++ b/monitor.h +@@ -63,6 +63,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 --git a/monitor_wrap.c b/monitor_wrap.c +index 8e379a15c..a4b66c56f 100644 +--- a/monitor_wrap.c ++++ b/monitor_wrap.c +@@ -1000,13 +1000,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) + } + + 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, +@@ -1019,4 +1021,57 @@ 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 --git a/monitor_wrap.h b/monitor_wrap.h +index 0df49c25b..830fdb308 100644 +--- a/monitor_wrap.h ++++ b/monitor_wrap.h +@@ -65,8 +65,10 @@ int mm_sshkey_verify(const struct sshkey *, const u_char *, size_t, + 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 --git a/readconf.c b/readconf.c +index cf7949884..55af0115d 100644 +--- a/readconf.c ++++ b/readconf.c +@@ -67,6 +67,7 @@ + #include "uidswap.h" + #include "myproposal.h" + #include "digest.h" ++#include "ssh-gss.h" + + /* Format of the configuration file: + +@@ -161,6 +162,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, +@@ -207,10 +210,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 }, +@@ -1122,10 +1137,46 @@ 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') { ++ error("%.200s line %d: Missing argument.", ++ filename, linenum); ++ goto out; ++ } ++ if (!kex_gss_names_valid(arg)) { ++ error("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.", ++ filename, linenum, arg ? arg : "<NONE>"); ++ goto out; ++ } ++ if (*activep && options->gss_kex_algorithms == NULL) ++ options->gss_kex_algorithms = xstrdup(arg); ++ break; ++ + case oBatchMode: + intptr = &options->batch_mode; + goto parse_flag; +@@ -2343,7 +2394,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; +@@ -2502,8 +2559,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) +@@ -3301,7 +3368,14 @@ dump_client_config(Options *o, const char *host) + 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 --git a/readconf.h b/readconf.h +index 2ce1b4c33..d0eb92362 100644 +--- a/readconf.h ++++ b/readconf.h +@@ -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 --git a/servconf.c b/servconf.c +index 2e039da8b..940de9e15 100644 +--- a/servconf.c ++++ b/servconf.c +@@ -70,6 +70,7 @@ + #include "auth.h" + #include "myproposal.h" + #include "digest.h" ++#include "ssh-gss.h" + + static void add_listen_addr(ServerOptions *, const char *, + const char *, int); +@@ -136,8 +137,11 @@ initialize_server_options(ServerOptions *options) + 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; +@@ -360,10 +364,18 @@ fill_default_server_options(ServerOptions *options) + 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) +@@ -520,6 +532,7 @@ typedef enum { + sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize, + sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, + sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, ++ sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey, + sAcceptEnv, sSetEnv, sPermitTunnel, + sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, + sUsePrivilegeSeparation, sAllowAgentForwarding, +@@ -602,12 +615,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 */ +@@ -1644,6 +1667,10 @@ process_server_config_line_depth(ServerOptions *options, char *line, + 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; +@@ -1652,6 +1679,22 @@ process_server_config_line_depth(ServerOptions *options, char *line, + 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 : "<NONE>"); ++ if (*activep && options->gss_kex_algorithms == NULL) ++ options->gss_kex_algorithms = xstrdup(arg); ++ break; ++ + case sPasswordAuthentication: + intptr = &options->password_authentication; + goto parse_flag; +@@ -3018,6 +3061,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 --git a/servconf.h b/servconf.h +index 7ad43de87..38a68d576 100644 +--- a/servconf.h ++++ b/servconf.h +@@ -141,8 +141,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 --git a/session.c b/session.c +index f30d7ac51..96960f2da 100644 +--- a/session.c ++++ b/session.c +@@ -2680,13 +2680,19 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt) + + #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 --git a/ssh-gss.h b/ssh-gss.h +index a8af117d2..6303ce185 100644 +--- a/ssh-gss.h ++++ b/ssh-gss.h +@@ -1,6 +1,6 @@ + /* $OpenBSD: ssh-gss.h,v 1.15 2021/01/27 10:05:28 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 +@@ -61,10 +61,34 @@ + + #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 ++ + typedef struct { + char *filename; + char *envvar; + char *envval; ++ struct passwd *owner; + void *data; + } ssh_gssapi_ccache; + +@@ -72,8 +96,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 +111,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 +122,11 @@ 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 */ + } 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); +@@ -109,6 +138,7 @@ OM_uint32 ssh_gssapi_test_oid_supported(OM_uint32 *, gss_OID, int *); + + 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, +@@ -123,17 +153,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 --git a/ssh-null.c b/ssh-null.c +new file mode 100644 +index 000000000..a934bda77 +--- /dev/null ++++ b/ssh-null.c +@@ -0,0 +1,108 @@ ++/* ++ * Copyright (c) 2023 Colin Watson. 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" ++ ++#ifdef GSSAPI ++ ++#include "sshbuf.h" ++#include "ssherr.h" ++#include "sshkey.h" ++ ++static int ++ssh_null_equal(const struct sshkey *a, const struct sshkey *b) ++{ ++ return 1; ++} ++ ++static int ++ssh_null_serialize_public(const struct sshkey *key, struct sshbuf *b, ++ enum sshkey_serialize_rep opts) ++{ ++ return SSH_ERR_KEY_TYPE_UNKNOWN; ++} ++ ++static int ++ssh_null_deserialize_public(const char *ktype, struct sshbuf *b, ++ struct sshkey *key) ++{ ++ return SSH_ERR_KEY_TYPE_UNKNOWN; ++} ++ ++static int ++ssh_null_serialize_private(const struct sshkey *key, struct sshbuf *b, ++ enum sshkey_serialize_rep opts) ++{ ++ return SSH_ERR_KEY_TYPE_UNKNOWN; ++} ++ ++static int ++ssh_null_deserialize_private(const char *ktype, struct sshbuf *b, ++ struct sshkey *key) ++{ ++ return SSH_ERR_KEY_TYPE_UNKNOWN; ++} ++ ++static int ++ssh_null_copy_public(const struct sshkey *from, struct sshkey *to) ++{ ++ return SSH_ERR_KEY_TYPE_UNKNOWN; ++} ++ ++static int ++ssh_null_verify(const struct sshkey *key, const u_char *sig, size_t siglen, ++ const u_char *data, size_t dlen, const char *alg, u_int compat, ++ struct sshkey_sig_details **detailsp) ++{ ++ return SSH_ERR_KEY_TYPE_UNKNOWN; ++} ++ ++static const struct sshkey_impl_funcs sshkey_null_funcs = { ++ /* .size = */ NULL, ++ /* .alloc = */ NULL, ++ /* .cleanup = */ NULL, ++ /* .equal = */ ssh_null_equal, ++ /* .ssh_serialize_public = */ ssh_null_serialize_public, ++ /* .ssh_deserialize_public = */ ssh_null_deserialize_public, ++ /* .ssh_serialize_private = */ ssh_null_serialize_private, ++ /* .ssh_deserialize_private = */ ssh_null_deserialize_private, ++ /* .generate = */ NULL, ++ /* .copy_public = */ ssh_null_copy_public, ++ /* .sign = */ NULL, ++ /* .verify = */ ssh_null_verify, ++}; ++ ++const struct sshkey_impl sshkey_null_impl = { ++ /* .name = */ "null", ++ /* .shortname = */ "null", ++ /* .sigalg = */ NULL, ++ /* .type = */ KEY_NULL, ++ /* .nid = */ 0, ++ /* .cert = */ 0, ++ /* .sigonly = */ 0, ++ /* .keybits = */ 0, ++ /* .funcs = */ &sshkey_null_funcs, ++}; ++ ++#endif /* GSSAPI */ +diff --git a/ssh.1 b/ssh.1 +index a3d1ba163..dfd80b8bc 100644 +--- a/ssh.1 ++++ b/ssh.1 +@@ -534,7 +534,13 @@ For full details of the options listed below, and their possible values, see + .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 +@@ -612,6 +618,8 @@ flag), + (supported message integrity codes), + .Ar kex + (key exchange algorithms), ++.Ar kex-gss ++(GSSAPI key exchange algorithms), + .Ar key + (key types), + .Ar key-cert +diff --git a/ssh.c b/ssh.c +index 87454b84a..855777d7f 100644 +--- a/ssh.c ++++ b/ssh.c +@@ -787,6 +787,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) +@@ -814,8 +816,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 --git a/ssh_config b/ssh_config +index 842ea866c..52aae8692 100644 +--- a/ssh_config ++++ b/ssh_config +@@ -24,6 +24,8 @@ + # HostbasedAuthentication no + # GSSAPIAuthentication no + # GSSAPIDelegateCredentials no ++# GSSAPIKeyExchange no ++# GSSAPITrustDNS no + # BatchMode no + # CheckHostIP yes + # AddressFamily any +diff --git a/ssh_config.5 b/ssh_config.5 +index 9eb6b9779..43f3b642c 100644 +--- a/ssh_config.5 ++++ b/ssh_config.5 +@@ -839,10 +839,67 @@ 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-gex-sha1-,gss-group14-sha1- . ++This option only applies to connections using GSSAPI. + .It Cm HashKnownHosts + Indicates that + .Xr ssh 1 +diff --git a/sshconnect2.c b/sshconnect2.c +index 58fe98db2..cb6a94e76 100644 +--- a/sshconnect2.c ++++ b/sshconnect2.c +@@ -81,8 +81,6 @@ + #endif + + /* import */ +-extern char *client_version_string; +-extern char *server_version_string; + extern Options options; + + /* +@@ -226,6 +224,11 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port, + char *prop_kex = NULL, *prop_enc = NULL, *prop_hostkey = 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; +@@ -269,6 +272,42 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port, + compat_pkalg_proposal(ssh, 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 ++ + if (options.rekey_limit || options.rekey_interval) + ssh_packet_set_rekey_limits(ssh, options.rekey_limit, + options.rekey_interval); +@@ -287,16 +326,46 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port, + # 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->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); + + /* remove ext-info from the KEX proposals for rekeying */ + myproposal[PROPOSAL_KEX_ALGS] = + compat_kex_proposal(ssh, options.kex_algorithms); ++#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 + if ((r = kex_prop2buf(ssh->kex->my, myproposal)) != 0) + fatal_r(r, "kex_prop2buf"); + +@@ -394,6 +463,7 @@ static int input_gssapi_response(int type, u_int32_t, struct ssh *); + 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 *); +@@ -410,6 +480,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, +@@ -785,12 +860,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 && +@@ -799,13 +894,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; + +@@ -1046,6 +1143,55 @@ input_gssapi_error(int type, u_int32_t plen, struct ssh *ssh) + 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 --git a/sshd.c b/sshd.c +index 6321936c0..6ad9a845a 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -803,8 +803,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); + } +@@ -1914,7 +1914,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); + } +@@ -2398,6 +2399,48 @@ do_ssh2_kex(struct ssh *ssh) + myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = prop_hostkey = + compat_pkalg_proposal(ssh, list_hostkey_types()); + ++#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) ++ newstr = gss; ++ 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] = "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"); +@@ -2413,7 +2456,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->load_host_public_key=&get_hostkey_public_by_type; +diff --git a/sshd_config b/sshd_config +index 36894ace5..ecfe8d026 100644 +--- a/sshd_config ++++ b/sshd_config +@@ -69,6 +69,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 --git a/sshd_config.5 b/sshd_config.5 +index 7313a7f79..83f840511 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -719,6 +719,11 @@ Specifies whether to automatically destroy the user's credentials cache + 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. +@@ -733,6 +738,31 @@ 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-gex-sha1-,gss-group14-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 --git a/sshkey.c b/sshkey.c +index 43712253d..35a423e92 100644 +--- a/sshkey.c ++++ b/sshkey.c +@@ -127,6 +127,9 @@ extern const struct sshkey_impl sshkey_dsa_cert_impl; + extern const struct sshkey_impl sshkey_xmss_impl; + extern const struct sshkey_impl sshkey_xmss_cert_impl; + #endif ++#ifdef GSSAPI ++extern const struct sshkey_impl sshkey_null_impl; ++#endif /* GSSAPI */ + + const struct sshkey_impl * const keyimpls[] = { + &sshkey_ed25519_impl, +@@ -164,6 +167,9 @@ const struct sshkey_impl * const keyimpls[] = { + &sshkey_xmss_impl, + &sshkey_xmss_cert_impl, + #endif ++#ifdef GSSAPI ++ &sshkey_null_impl, ++#endif /* GSSAPI */ + NULL + }; + +@@ -319,7 +325,7 @@ sshkey_alg_list(int certs_only, int plain_only, int include_sigonly, char sep) + + 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 --git a/sshkey.h b/sshkey.h +index 771c4bcee..a0742e8c2 100644 +--- a/sshkey.h ++++ b/sshkey.h +@@ -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/debian/patches/keepalive-extensions.patch b/debian/patches/keepalive-extensions.patch new file mode 100644 index 0000000..6df9b9e --- /dev/null +++ b/debian/patches/keepalive-extensions.patch @@ -0,0 +1,135 @@ +From dbc7024bb9fe29a5d2bd398219ae2fc5668826b8 Mon Sep 17 00:00:00 2001 +From: Richard Kettlewell <rjk@greenend.org.uk> +Date: Sun, 9 Feb 2014 16:09:52 +0000 +Subject: Various keepalive extensions + +Add compatibility aliases for ProtocolKeepAlives and SetupTimeOut, supported +in previous versions of Debian's OpenSSH package but since superseded by +ServerAliveInterval. (We're probably stuck with this bit for +compatibility.) + +In batch mode, default ServerAliveInterval to five minutes. + +Adjust documentation to match and to give some more advice on use of +keepalives. + +Author: Ian Jackson <ian@chiark.greenend.org.uk> +Author: Matthew Vernon <matthew@debian.org> +Author: Colin Watson <cjwatson@debian.org> +Last-Update: 2023-01-02 + +Patch-Name: keepalive-extensions.patch +--- + readconf.c | 14 ++++++++++++-- + ssh_config.5 | 21 +++++++++++++++++++-- + sshd_config.5 | 3 +++ + 3 files changed, 34 insertions(+), 4 deletions(-) + +diff --git a/readconf.c b/readconf.c +index 4a39f5b66..9ac71133a 100644 +--- a/readconf.c ++++ b/readconf.c +@@ -179,6 +179,7 @@ typedef enum { + oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump, + oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize, + oEnableEscapeCommandline, ++ oProtocolKeepAlives, oSetupTimeOut, + oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported + } OpCodes; + +@@ -339,6 +340,8 @@ static struct { + { "knownhostscommand", oKnownHostsCommand }, + { "requiredrsasize", oRequiredRSASize }, + { "enableescapecommandline", oEnableEscapeCommandline }, ++ { "protocolkeepalives", oProtocolKeepAlives }, ++ { "setuptimeout", oSetupTimeOut }, + + { NULL, oBadOption } + }; +@@ -1772,6 +1775,8 @@ parse_pubkey_algos: + goto parse_flag; + + case oServerAliveInterval: ++ case oProtocolKeepAlives: /* Debian-specific compatibility alias */ ++ case oSetupTimeOut: /* Debian-specific compatibility alias */ + intptr = &options->server_alive_interval; + goto parse_time; + +@@ -2654,8 +2659,13 @@ fill_default_options(Options * options) + options->rekey_interval = 0; + if (options->verify_host_key_dns == -1) + options->verify_host_key_dns = 0; +- if (options->server_alive_interval == -1) +- options->server_alive_interval = 0; ++ if (options->server_alive_interval == -1) { ++ /* in batch mode, default is 5mins */ ++ if (options->batch_mode == 1) ++ options->server_alive_interval = 300; ++ else ++ options->server_alive_interval = 0; ++ } + if (options->server_alive_count_max == -1) + options->server_alive_count_max = 3; + if (options->control_master == -1) +diff --git a/ssh_config.5 b/ssh_config.5 +index 43f3b642c..09905bff3 100644 +--- a/ssh_config.5 ++++ b/ssh_config.5 +@@ -275,9 +275,13 @@ If set to + .Cm yes , + user interaction such as password prompts and host key confirmation requests + will be disabled. ++In addition, the ++.Cm ServerAliveInterval ++option will be set to 300 seconds by default (Debian-specific). + This option is useful in scripts and other batch jobs where no user + is present to interact with +-.Xr ssh 1 . ++.Xr ssh 1 , ++and where it is desirable to detect a broken network swiftly. + The argument must be + .Cm yes + or +@@ -1794,7 +1798,14 @@ from the server, + will send a message through the encrypted + channel to request a response from the server. + The default +-is 0, indicating that these messages will not be sent to the server. ++is 0, indicating that these messages will not be sent to the server, ++or 300 if the ++.Cm BatchMode ++option is set (Debian-specific). ++.Cm ProtocolKeepAlives ++and ++.Cm SetupTimeOut ++are Debian-specific compatibility aliases for this option. + .It Cm SessionType + May be used to either request invocation of a subsystem on the remote system, + or to prevent the execution of a remote command at all. +@@ -1908,6 +1919,12 @@ Specifies whether the system should send TCP keepalive messages to the + other side. + If they are sent, death of the connection or crash of one + of the machines will be properly noticed. ++This option only uses TCP keepalives (as opposed to using ssh level ++keepalives), so takes a long time to notice when the connection dies. ++As such, you probably want ++the ++.Cm ServerAliveInterval ++option as well. + However, this means that + connections will die if the route is down temporarily, and some people + find it annoying. +diff --git a/sshd_config.5 b/sshd_config.5 +index 83f840511..eb58750a5 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -1830,6 +1830,9 @@ This avoids infinitely hanging sessions. + .Pp + To disable TCP keepalive messages, the value should be set to + .Cm no . ++.Pp ++This option was formerly called ++.Cm KeepAlive . + .It Cm TrustedUserCAKeys + Specifies a file containing public keys of certificate authorities that are + trusted to sign user certificates for authentication, or diff --git a/debian/patches/maxhostnamelen.patch b/debian/patches/maxhostnamelen.patch new file mode 100644 index 0000000..fd53e7f --- /dev/null +++ b/debian/patches/maxhostnamelen.patch @@ -0,0 +1,30 @@ +From 36b00b5f4d96d6d9db3fd9e418bd2d1f66e8e7fe Mon Sep 17 00:00:00 2001 +From: Svante Signell <svante.signell@gmail.com> +Date: Fri, 5 Nov 2021 23:22:53 +0000 +Subject: Define MAXHOSTNAMELEN on GNU/Hurd + +Bug-Debian: https://bugs.debian.org/997030 +Last-Update: 2021-11-05 + +Patch-Name: maxhostnamelen.patch +--- + defines.h | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/defines.h b/defines.h +index 279e509aa..7225cbfab 100644 +--- a/defines.h ++++ b/defines.h +@@ -136,6 +136,12 @@ enum + # endif + #endif /* HOST_NAME_MAX */ + ++#ifndef MAXHOSTNAMELEN ++# if defined(_POSIX_HOST_NAME_MAX) ++# define MAXHOSTNAMELEN _POSIX_HOST_NAME_MAX ++# endif ++#endif /* MAXHOSTNAMELEN */ ++ + #if defined(HAVE_DECL_MAXSYMLINKS) && HAVE_DECL_MAXSYMLINKS == 0 + # define MAXSYMLINKS 5 + #endif diff --git a/debian/patches/mention-ssh-keygen-on-keychange.patch b/debian/patches/mention-ssh-keygen-on-keychange.patch new file mode 100644 index 0000000..71151ff --- /dev/null +++ b/debian/patches/mention-ssh-keygen-on-keychange.patch @@ -0,0 +1,44 @@ +From e797fa7ecced95a0b7f27b000e467ffb31934d28 Mon Sep 17 00:00:00 2001 +From: Scott Moser <smoser@ubuntu.com> +Date: Sun, 9 Feb 2014 16:10:03 +0000 +Subject: Mention ssh-keygen in ssh fingerprint changed warning + +Author: Chris Lamb <lamby@debian.org> +Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1843 +Bug-Ubuntu: https://bugs.launchpad.net/bugs/686607 +Last-Update: 2017-08-22 + +Patch-Name: mention-ssh-keygen-on-keychange.patch +--- + sshconnect.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/sshconnect.c b/sshconnect.c +index 891e968dc..77a3080ba 100644 +--- a/sshconnect.c ++++ b/sshconnect.c +@@ -1270,9 +1270,13 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo, + error("%s. This could either mean that", key_msg); + error("DNS SPOOFING is happening or the IP address for the host"); + error("and its host key have changed at the same time."); +- if (ip_status != HOST_NEW) ++ if (ip_status != HOST_NEW) { + error("Offending key for IP in %s:%lu", + ip_found->file, ip_found->line); ++ error(" remove with:"); ++ error(" ssh-keygen -f \"%s\" -R \"%s\"", ++ ip_found->file, ip); ++ } + } + /* The host key has changed. */ + warn_changed_key(host_key); +@@ -1284,6 +1288,9 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo, + error("Offending %s key in %s:%lu", + sshkey_type(host_found->key), + host_found->file, host_found->line); ++ error(" remove with:"); ++ error(" ssh-keygen -f \"%s\" -R \"%s\"", ++ host_found->file, host); + + /* + * If strict host key checking is in use, the user will have diff --git a/debian/patches/no-openssl-version-status.patch b/debian/patches/no-openssl-version-status.patch new file mode 100644 index 0000000..4babac9 --- /dev/null +++ b/debian/patches/no-openssl-version-status.patch @@ -0,0 +1,62 @@ +From c7c2ce00f07135457dbd924cfe962e03a2b0ab62 Mon Sep 17 00:00:00 2001 +From: Kurt Roeckx <kurt@roeckx.be> +Date: Sun, 9 Feb 2014 16:10:14 +0000 +Subject: Don't check the status field of the OpenSSL version + +There is no reason to check the version of OpenSSL (in Debian). If it's +not compatible the soname will change. OpenSSH seems to want to do a +check for the soname based on the version number, but wants to keep the +status of the release the same. Remove that check on the status since +it doesn't tell you anything about how compatible that version is. + +Author: Colin Watson <cjwatson@debian.org> +Bug-Debian: https://bugs.debian.org/93581 +Bug-Debian: https://bugs.debian.org/664383 +Bug-Debian: https://bugs.debian.org/732940 +Forwarded: not-needed +Last-Update: 2014-10-07 + +Patch-Name: no-openssl-version-status.patch +--- + openbsd-compat/openssl-compat.c | 6 +++--- + openbsd-compat/regress/opensslvertest.c | 1 + + 2 files changed, 4 insertions(+), 3 deletions(-) + +diff --git a/openbsd-compat/openssl-compat.c b/openbsd-compat/openssl-compat.c +index a37ca61bf..c1749210d 100644 +--- a/openbsd-compat/openssl-compat.c ++++ b/openbsd-compat/openssl-compat.c +@@ -34,7 +34,7 @@ + /* + * OpenSSL version numbers: MNNFFPPS: major minor fix patch status + * We match major, minor, fix and status (not patch) for <1.0.0. +- * After that, we acceptable compatible fix versions (so we ++ * After that, we accept compatible fix and status versions (so we + * allow 1.0.1 to work with 1.0.0). Going backwards is only allowed + * within a patch series. + */ +@@ -55,10 +55,10 @@ ssh_compatible_openssl(long headerver, long libver) + } + + /* +- * For versions >= 1.0.0, major,minor,status must match and library ++ * For versions >= 1.0.0, major,minor must match and library + * fix version must be equal to or newer than the header. + */ +- mask = 0xfff0000fL; /* major,minor,status */ ++ mask = 0xfff00000L; /* major,minor */ + hfix = (headerver & 0x000ff000) >> 12; + lfix = (libver & 0x000ff000) >> 12; + if ( (headerver & mask) == (libver & mask) && lfix >= hfix) +diff --git a/openbsd-compat/regress/opensslvertest.c b/openbsd-compat/regress/opensslvertest.c +index d50066609..aeccc107b 100644 +--- a/openbsd-compat/regress/opensslvertest.c ++++ b/openbsd-compat/regress/opensslvertest.c +@@ -37,6 +37,7 @@ struct version_test { + + /* built with 1.0.1b release headers */ + { 0x1000101fL, 0x1000101fL, 1},/* exact match */ ++ { 0x1000101fL, 0x10001010L, 1}, /* different status: ok */ + { 0x1000101fL, 0x1000102fL, 1}, /* newer library patch version: ok */ + { 0x1000101fL, 0x1000100fL, 1}, /* older library patch version: ok */ + { 0x1000101fL, 0x1000201fL, 1}, /* newer library fix version: ok */ diff --git a/debian/patches/openbsd-docs.patch b/debian/patches/openbsd-docs.patch new file mode 100644 index 0000000..6312b84 --- /dev/null +++ b/debian/patches/openbsd-docs.patch @@ -0,0 +1,255 @@ +From f8033f154f0fe23f974f67ba2f8a29754a5044af Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Sun, 9 Feb 2014 16:10:09 +0000 +Subject: Adjust various OpenBSD-specific references in manual pages + +No single bug reference for this patch, but history includes: + https://bugs.debian.org/154434 (login.conf(5)) + https://bugs.debian.org/513417 (/etc/rc) + https://bugs.debian.org/530692 (ssl(8)) + https://bugs.launchpad.net/bugs/456660 (ssl(8)) + https://bugs.debian.org/998069 (rdomain(4)) + +Forwarded: not-needed +Last-Update: 2021-11-05 + +Patch-Name: openbsd-docs.patch +--- + moduli.5 | 4 ++-- + ssh-keygen.1 | 12 ++++-------- + ssh.1 | 4 ++++ + sshd.8 | 5 ++--- + sshd_config.5 | 40 ++-------------------------------------- + 5 files changed, 14 insertions(+), 51 deletions(-) + +diff --git a/moduli.5 b/moduli.5 +index 5086a6d42..6dffdc7e6 100644 +--- a/moduli.5 ++++ b/moduli.5 +@@ -21,7 +21,7 @@ + .Nd Diffie-Hellman moduli + .Sh DESCRIPTION + The +-.Pa /etc/moduli ++.Pa /etc/ssh/moduli + file contains prime numbers and generators for use by + .Xr sshd 8 + in the Diffie-Hellman Group Exchange key exchange method. +@@ -110,7 +110,7 @@ first estimates the size of the modulus required to produce enough + Diffie-Hellman output to sufficiently key the selected symmetric cipher. + .Xr sshd 8 + then randomly selects a modulus from +-.Fa /etc/moduli ++.Fa /etc/ssh/moduli + that best meets the size requirement. + .Sh SEE ALSO + .Xr ssh-keygen 1 , +diff --git a/ssh-keygen.1 b/ssh-keygen.1 +index 8b1f617d2..f2a021878 100644 +--- a/ssh-keygen.1 ++++ b/ssh-keygen.1 +@@ -212,9 +212,7 @@ key in + .Pa ~/.ssh/id_ed25519_sk + or + .Pa ~/.ssh/id_rsa . +-Additionally, the system administrator may use this to generate host keys, +-as seen in +-.Pa /etc/rc . ++Additionally, the system administrator may use this to generate host keys. + .Pp + Normally this program generates the key and asks for a file in which + to store the private key. +@@ -279,9 +277,7 @@ If + .Fl f + has also been specified, its argument is used as a prefix to the + default path for the resulting host key files. +-This is used by +-.Pa /etc/rc +-to generate new host keys. ++This is used by system administration scripts to generate new host keys. + .It Fl a Ar rounds + When saving a private key, this option specifies the number of KDF + (key derivation function, currently +@@ -849,7 +845,7 @@ option. + Valid generator values are 2, 3, and 5. + .Pp + Screened DH groups may be installed in +-.Pa /etc/moduli . ++.Pa /etc/ssh/moduli . + It is important that this file contains moduli of a range of bit lengths. + .Pp + A number of options are available for moduli generation and screening via the +@@ -1307,7 +1303,7 @@ on all machines + where the user wishes to log in using public key authentication. + There is no need to keep the contents of this file secret. + .Pp +-.It Pa /etc/moduli ++.It Pa /etc/ssh/moduli + Contains Diffie-Hellman groups used for DH-GEX. + The file format is described in + .Xr moduli 5 . +diff --git a/ssh.1 b/ssh.1 +index 4028f971a..6b56f5638 100644 +--- a/ssh.1 ++++ b/ssh.1 +@@ -925,6 +925,10 @@ implements public key authentication protocol automatically, + using one of the DSA, ECDSA, Ed25519 or RSA algorithms. + The HISTORY section of + .Xr ssl 8 ++(on non-OpenBSD systems, see ++.nh ++http://www.openbsd.org/cgi\-bin/man.cgi?query=ssl&sektion=8#HISTORY) ++.hy + contains a brief discussion of the DSA and RSA algorithms. + .Pp + The file +diff --git a/sshd.8 b/sshd.8 +index 71da859dc..ec1687585 100644 +--- a/sshd.8 ++++ b/sshd.8 +@@ -64,7 +64,7 @@ over an insecure network. + .Nm + listens for connections from clients. + It is normally started at boot from +-.Pa /etc/rc . ++.Pa /etc/init.d/ssh . + It forks a new + daemon for each incoming connection. + The forked daemons handle +@@ -921,7 +921,7 @@ This file is for host-based authentication (see + .Xr ssh 1 ) . + It should only be writable by root. + .Pp +-.It Pa /etc/moduli ++.It Pa /etc/ssh/moduli + Contains Diffie-Hellman groups used for the "Diffie-Hellman Group Exchange" + key exchange method. + The file format is described in +@@ -1019,7 +1019,6 @@ The content of this file is not sensitive; it can be world-readable. + .Xr ssh-keyscan 1 , + .Xr chroot 2 , + .Xr hosts_access 5 , +-.Xr login.conf 5 , + .Xr moduli 5 , + .Xr sshd_config 5 , + .Xr inetd 8 , +diff --git a/sshd_config.5 b/sshd_config.5 +index 2c6ec76cb..b9a19c7bd 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -981,9 +981,6 @@ for interactive sessions and + for non-interactive sessions. + .It Cm KbdInteractiveAuthentication + Specifies whether to allow keyboard-interactive authentication. +-All authentication styles from +-.Xr login.conf 5 +-are supported. + The default is + .Cm yes . + The argument to this keyword must be +@@ -1087,45 +1084,33 @@ The following forms may be used: + .Sm off + .Ar hostname | address + .Sm on +-.Op Cm rdomain Ar domain + .It + .Cm ListenAddress + .Sm off + .Ar hostname : port + .Sm on +-.Op Cm rdomain Ar domain + .It + .Cm ListenAddress + .Sm off + .Ar IPv4_address : port + .Sm on +-.Op Cm rdomain Ar domain + .It + .Cm ListenAddress + .Sm off + .Oo Ar hostname | address Oc : Ar port + .Sm on +-.Op Cm rdomain Ar domain + .El + .Pp +-The optional +-.Cm rdomain +-qualifier requests +-.Xr sshd 8 +-listen in an explicit routing domain. + If + .Ar port + is not specified, + sshd will listen on the address and all + .Cm Port + options specified. +-The default is to listen on all local addresses on the current default +-routing domain. ++The default is to listen on all local addresses. + Multiple + .Cm ListenAddress + options are permitted. +-For more information on routing domains, see +-.Xr rdomain 4 . + .It Cm LoginGraceTime + The server disconnects after this time if the user has not + successfully logged in. +@@ -1250,14 +1235,8 @@ The available criteria are + .Cm Host , + .Cm LocalAddress , + .Cm LocalPort , +-.Cm RDomain , + and +-.Cm Address +-(with +-.Cm RDomain +-representing the +-.Xr rdomain 4 +-on which the connection was received). ++.Cm Address . + .Pp + The match patterns may consist of single entries or comma-separated + lists and may use the wildcard and negation operators described in the +@@ -1329,7 +1308,6 @@ Available keywords are + .Cm PubkeyAuthOptions , + .Cm RekeyLimit , + .Cm RevokedKeys , +-.Cm RDomain , + .Cm SetEnv , + .Cm StreamLocalBindMask , + .Cm StreamLocalBindUnlink , +@@ -1724,15 +1702,6 @@ an OpenSSH Key Revocation List (KRL) as generated by + .Xr ssh-keygen 1 . + For more information on KRLs, see the KEY REVOCATION LISTS section in + .Xr ssh-keygen 1 . +-.It Cm RDomain +-Specifies an explicit routing domain that is applied after authentication +-has completed. +-The user session, as well as any forwarded or listening IP sockets, +-will be bound to this +-.Xr rdomain 4 . +-If the routing domain is set to +-.Cm \&%D , +-then the domain in which the incoming connection was received will be applied. + .It Cm SecurityKeyProvider + Specifies a path to a library that will be used when loading + FIDO authenticator-hosted keys, overriding the default of using +@@ -2047,8 +2016,6 @@ which are expanded at runtime: + .It %% + A literal + .Sq % . +-.It \&%D +-The routing domain in which the incoming connection was received. + .It %F + The fingerprint of the CA key. + .It %f +@@ -2087,9 +2054,6 @@ accepts the tokens %%, %h, %U, and %u. + .Pp + .Cm ChrootDirectory + accepts the tokens %%, %h, %U, and %u. +-.Pp +-.Cm RoutingDomain +-accepts the token %D. + .Sh FILES + .Bl -tag -width Ds + .It Pa /etc/ssh/sshd_config diff --git a/debian/patches/package-versioning.patch b/debian/patches/package-versioning.patch new file mode 100644 index 0000000..8529c23 --- /dev/null +++ b/debian/patches/package-versioning.patch @@ -0,0 +1,47 @@ +From 720ad1a8e62ff52438766b49f8413ac55b17f570 Mon Sep 17 00:00:00 2001 +From: Matthew Vernon <matthew@debian.org> +Date: Sun, 9 Feb 2014 16:10:05 +0000 +Subject: Include the Debian version in our identification + +This makes it easier to audit networks for versions patched against security +vulnerabilities. It has little detrimental effect, as attackers will +generally just try attacks rather than bothering to scan for +vulnerable-looking version strings. (However, see debian-banner.patch.) + +Forwarded: not-needed +Last-Update: 2021-11-05 + +Patch-Name: package-versioning.patch +--- + kex.c | 2 +- + version.h | 7 ++++++- + 2 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/kex.c b/kex.c +index c259e00fc..32858e65c 100644 +--- a/kex.c ++++ b/kex.c +@@ -1290,7 +1290,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms, + if (version_addendum != NULL && *version_addendum == '\0') + version_addendum = NULL; + if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n", +- PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION, ++ PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_RELEASE, + version_addendum == NULL ? "" : " ", + version_addendum == NULL ? "" : version_addendum)) != 0) { + oerrno = errno; +diff --git a/version.h b/version.h +index d83ae5b94..a7d5ecef6 100644 +--- a/version.h ++++ b/version.h +@@ -3,4 +3,9 @@ + #define SSH_VERSION "OpenSSH_9.2" + + #define SSH_PORTABLE "p1" +-#define SSH_RELEASE SSH_VERSION SSH_PORTABLE ++#define SSH_RELEASE_MINIMUM SSH_VERSION SSH_PORTABLE ++#ifdef SSH_EXTRAVERSION ++#define SSH_RELEASE SSH_RELEASE_MINIMUM " " SSH_EXTRAVERSION ++#else ++#define SSH_RELEASE SSH_RELEASE_MINIMUM ++#endif diff --git a/debian/patches/remove-spurious-ssh-agent-options.patch b/debian/patches/remove-spurious-ssh-agent-options.patch new file mode 100644 index 0000000..ee02fb3 --- /dev/null +++ b/debian/patches/remove-spurious-ssh-agent-options.patch @@ -0,0 +1,29 @@ +From 74edce484429249265baaee1e8a5d1785ee7afa7 Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Tue, 7 Feb 2023 23:55:19 +0000 +Subject: Remove spurious ssh-agent options + +These cause regress/agent-getpeereid.sh to leave a stray ssh-agent +process lying around. + +Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=3536 +Last-Update: 2023-02-07 + +Patch-Name: remove-spurious-ssh-agent-options.patch +--- + regress/agent-getpeereid.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/regress/agent-getpeereid.sh b/regress/agent-getpeereid.sh +index 79e9c7d9e..e0379445d 100644 +--- a/regress/agent-getpeereid.sh ++++ b/regress/agent-getpeereid.sh +@@ -53,7 +53,7 @@ else + fi + + trace "kill agent" +- ${SSHAGENT} -vvv -k >>$OBJ/ssh-agent.log 2>&1 ++ ${SSHAGENT} -k >>$OBJ/ssh-agent.log 2>&1 + fi + + rm -f ${OBJ}/agent diff --git a/debian/patches/restore-authorized_keys2.patch b/debian/patches/restore-authorized_keys2.patch new file mode 100644 index 0000000..918da0f --- /dev/null +++ b/debian/patches/restore-authorized_keys2.patch @@ -0,0 +1,35 @@ +From b2cc972d55fcc3c3df709a340ce3019fec9880c4 Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Sun, 5 Mar 2017 02:02:11 +0000 +Subject: Restore reading authorized_keys2 by default + +Upstream seems to intend to gradually phase this out, so don't assume +that this will remain the default forever. However, we were late in +adopting the upstream sshd_config changes, so it makes sense to extend +the grace period. + +Bug-Debian: https://bugs.debian.org/852320 +Forwarded: not-needed +Last-Update: 2017-03-05 + +Patch-Name: restore-authorized_keys2.patch +--- + sshd_config | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/sshd_config b/sshd_config +index 677f97d5d..d500d18cd 100644 +--- a/sshd_config ++++ b/sshd_config +@@ -38,9 +38,8 @@ Include /etc/ssh/sshd_config.d/*.conf + + #PubkeyAuthentication yes + +-# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 +-# but this is overridden so installations will only check .ssh/authorized_keys +-AuthorizedKeysFile .ssh/authorized_keys ++# Expect .ssh/authorized_keys2 to be disregarded by default in future. ++#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 + + #AuthorizedPrincipalsFile none + diff --git a/debian/patches/restore-tcp-wrappers.patch b/debian/patches/restore-tcp-wrappers.patch new file mode 100644 index 0000000..404c696 --- /dev/null +++ b/debian/patches/restore-tcp-wrappers.patch @@ -0,0 +1,172 @@ +From 8cee1ce3e07ac7904468ab8076ad5595048fb4c9 Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Tue, 7 Oct 2014 13:22:41 +0100 +Subject: Restore TCP wrappers support + +Support for TCP wrappers was dropped in OpenSSH 6.7. See this message +and thread: + + https://lists.mindrot.org/pipermail/openssh-unix-dev/2014-April/032497.html + +It is true that this reduces preauth attack surface in sshd. On the +other hand, this support seems to be quite widely used, and abruptly +dropping it (from the perspective of users who don't read +openssh-unix-dev) could easily cause more serious problems in practice. + +It's not entirely clear what the right long-term answer for Debian is, +but it at least probably doesn't involve dropping this feature shortly +before a freeze. + +Forwarded: not-needed +Last-Update: 2022-02-23 + +Patch-Name: restore-tcp-wrappers.patch +--- + configure.ac | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + sshd.8 | 7 +++++++ + sshd.c | 25 +++++++++++++++++++++++ + 3 files changed, 89 insertions(+) + +diff --git a/configure.ac b/configure.ac +index 75a9e5670..0c59c8b50 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1667,6 +1667,62 @@ else + AC_MSG_RESULT([no]) + fi + ++# Check whether user wants TCP wrappers support ++TCPW_MSG="no" ++AC_ARG_WITH([tcp-wrappers], ++ [ --with-tcp-wrappers[[=PATH]] Enable tcpwrappers support (optionally in PATH)], ++ [ ++ if test "x$withval" != "xno" ; then ++ saved_LIBS="$LIBS" ++ saved_LDFLAGS="$LDFLAGS" ++ saved_CPPFLAGS="$CPPFLAGS" ++ if test -n "${withval}" && \ ++ test "x${withval}" != "xyes"; then ++ if test -d "${withval}/lib"; then ++ if test -n "${need_dash_r}"; then ++ LDFLAGS="-L${withval}/lib -R${withval}/lib ${LDFLAGS}" ++ else ++ LDFLAGS="-L${withval}/lib ${LDFLAGS}" ++ fi ++ else ++ if test -n "${need_dash_r}"; then ++ LDFLAGS="-L${withval} -R${withval} ${LDFLAGS}" ++ else ++ LDFLAGS="-L${withval} ${LDFLAGS}" ++ fi ++ fi ++ if test -d "${withval}/include"; then ++ CPPFLAGS="-I${withval}/include ${CPPFLAGS}" ++ else ++ CPPFLAGS="-I${withval} ${CPPFLAGS}" ++ fi ++ fi ++ LIBS="-lwrap $LIBS" ++ AC_MSG_CHECKING([for libwrap]) ++ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ ++#include <sys/types.h> ++#include <sys/socket.h> ++#include <netinet/in.h> ++#include <tcpd.h> ++int deny_severity = 0, allow_severity = 0; ++ ]], [[ ++ hosts_access(0); ++ ]])], [ ++ AC_MSG_RESULT([yes]) ++ AC_DEFINE([LIBWRAP], [1], ++ [Define if you want ++ TCP Wrappers support]) ++ SSHDLIBS="$SSHDLIBS -lwrap" ++ TCPW_MSG="yes" ++ ], [ ++ AC_MSG_ERROR([*** libwrap missing]) ++ ++ ]) ++ LIBS="$saved_LIBS" ++ fi ++ ] ++) ++ + # Check whether user wants to use ldns + LDNS_MSG="no" + AC_ARG_WITH(ldns, +@@ -5653,6 +5709,7 @@ echo " PAM support: $PAM_MSG" + echo " OSF SIA support: $SIA_MSG" + echo " KerberosV support: $KRB5_MSG" + echo " SELinux support: $SELINUX_MSG" ++echo " TCP Wrappers support: $TCPW_MSG" + echo " libedit support: $LIBEDIT_MSG" + echo " libldns support: $LDNS_MSG" + echo " Solaris process contract support: $SPC_MSG" +diff --git a/sshd.8 b/sshd.8 +index bace97839..71da859dc 100644 +--- a/sshd.8 ++++ b/sshd.8 +@@ -910,6 +910,12 @@ the user's home directory becomes accessible. + This file should be writable only by the user, and need not be + readable by anyone else. + .Pp ++.It Pa /etc/hosts.allow ++.It Pa /etc/hosts.deny ++Access controls that should be enforced by tcp-wrappers are defined here. ++Further details are described in ++.Xr hosts_access 5 . ++.Pp + .It Pa /etc/hosts.equiv + This file is for host-based authentication (see + .Xr ssh 1 ) . +@@ -1012,6 +1018,7 @@ The content of this file is not sensitive; it can be world-readable. + .Xr ssh-keygen 1 , + .Xr ssh-keyscan 1 , + .Xr chroot 2 , ++.Xr hosts_access 5 , + .Xr login.conf 5 , + .Xr moduli 5 , + .Xr sshd_config 5 , +diff --git a/sshd.c b/sshd.c +index 6ad9a845a..7944cf633 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -129,6 +129,13 @@ + #include "srclimit.h" + #include "dh.h" + ++#ifdef LIBWRAP ++#include <tcpd.h> ++#include <syslog.h> ++int allow_severity; ++int deny_severity; ++#endif /* LIBWRAP */ ++ + /* Re-exec fds */ + #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) + #define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2) +@@ -2189,6 +2196,24 @@ main(int ac, char **av) + #ifdef SSH_AUDIT_EVENTS + audit_connection_from(remote_ip, remote_port); + #endif ++#ifdef LIBWRAP ++ allow_severity = options.log_facility|LOG_INFO; ++ deny_severity = options.log_facility|LOG_WARNING; ++ /* Check whether logins are denied from this host. */ ++ if (ssh_packet_connection_is_on_socket(ssh)) { ++ struct request_info req; ++ ++ request_init(&req, RQ_DAEMON, __progname, RQ_FILE, sock_in, 0); ++ fromhost(&req); ++ ++ if (!hosts_access(&req)) { ++ debug("Connection refused by tcp wrapper"); ++ refuse(&req); ++ /* NOTREACHED */ ++ fatal("libwrap refuse returns"); ++ } ++ } ++#endif /* LIBWRAP */ + + rdomain = ssh_packet_rdomain_in(ssh); + diff --git a/debian/patches/revert-ipqos-defaults.patch b/debian/patches/revert-ipqos-defaults.patch new file mode 100644 index 0000000..9ea3ad6 --- /dev/null +++ b/debian/patches/revert-ipqos-defaults.patch @@ -0,0 +1,93 @@ +From 8aea1d66b4ba0afd6cb4b25991bfb683d951c6e2 Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Mon, 8 Apr 2019 10:46:29 +0100 +Subject: Revert "upstream: Update default IPQoS in ssh(1), sshd(8) to DSCP + AF21 for" + +This reverts commit 5ee8448ad7c306f05a9f56769f95336a8269f379. + +The IPQoS default changes have some unfortunate interactions with +iptables (see https://bugs.debian.org/923880) and VMware, so I'm +temporarily reverting them until those have been fixed. + +Bug-Debian: https://bugs.debian.org/923879 +Bug-Debian: https://bugs.debian.org/926229 +Bug-Ubuntu: https://bugs.launchpad.net/bugs/1822370 +Last-Update: 2019-04-08 + +Patch-Name: revert-ipqos-defaults.patch +--- + readconf.c | 4 ++-- + servconf.c | 4 ++-- + ssh_config.5 | 6 ++---- + sshd_config.5 | 6 ++---- + 4 files changed, 8 insertions(+), 12 deletions(-) + +diff --git a/readconf.c b/readconf.c +index 6c8ad919f..2db9cb6ae 100644 +--- a/readconf.c ++++ b/readconf.c +@@ -2686,9 +2686,9 @@ fill_default_options(Options * options) + if (options->visual_host_key == -1) + options->visual_host_key = 0; + if (options->ip_qos_interactive == -1) +- options->ip_qos_interactive = IPTOS_DSCP_AF21; ++ options->ip_qos_interactive = IPTOS_LOWDELAY; + if (options->ip_qos_bulk == -1) +- options->ip_qos_bulk = IPTOS_DSCP_CS1; ++ options->ip_qos_bulk = IPTOS_THROUGHPUT; + if (options->request_tty == -1) + options->request_tty = REQUEST_TTY_AUTO; + if (options->session_type == -1) +diff --git a/servconf.c b/servconf.c +index 4ecbca8ed..8b824e35a 100644 +--- a/servconf.c ++++ b/servconf.c +@@ -441,9 +441,9 @@ fill_default_server_options(ServerOptions *options) + if (options->permit_tun == -1) + options->permit_tun = SSH_TUNMODE_NO; + if (options->ip_qos_interactive == -1) +- options->ip_qos_interactive = IPTOS_DSCP_AF21; ++ options->ip_qos_interactive = IPTOS_LOWDELAY; + if (options->ip_qos_bulk == -1) +- options->ip_qos_bulk = IPTOS_DSCP_CS1; ++ options->ip_qos_bulk = IPTOS_THROUGHPUT; + if (options->version_addendum == NULL) + options->version_addendum = xstrdup(""); + if (options->fwd_opts.streamlocal_bind_mask == (mode_t)-1) +diff --git a/ssh_config.5 b/ssh_config.5 +index 32851c984..f8616c18b 100644 +--- a/ssh_config.5 ++++ b/ssh_config.5 +@@ -1221,11 +1221,9 @@ If one argument is specified, it is used as the packet class unconditionally. + If two values are specified, the first is automatically selected for + interactive sessions and the second for non-interactive sessions. + The default is +-.Cm af21 +-(Low-Latency Data) ++.Cm lowdelay + for interactive sessions and +-.Cm cs1 +-(Lower Effort) ++.Cm throughput + for non-interactive sessions. + .It Cm KbdInteractiveAuthentication + Specifies whether to use keyboard-interactive authentication. +diff --git a/sshd_config.5 b/sshd_config.5 +index b13f7665b..7fd8abf48 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -1002,11 +1002,9 @@ If one argument is specified, it is used as the packet class unconditionally. + If two values are specified, the first is automatically selected for + interactive sessions and the second for non-interactive sessions. + The default is +-.Cm af21 +-(Low-Latency Data) ++.Cm lowdelay + for interactive sessions and +-.Cm cs1 +-(Lower Effort) ++.Cm throughput + for non-interactive sessions. + .It Cm KbdInteractiveAuthentication + Specifies whether to allow keyboard-interactive authentication. diff --git a/debian/patches/scp-quoting.patch b/debian/patches/scp-quoting.patch new file mode 100644 index 0000000..2419730 --- /dev/null +++ b/debian/patches/scp-quoting.patch @@ -0,0 +1,41 @@ +From 501d8554b6792531778d6b3b9344f8e55d84df29 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Nicolas=20Valc=C3=A1rcel?= <nvalcarcel@ubuntu.com> +Date: Sun, 9 Feb 2014 16:09:59 +0000 +Subject: Adjust scp quoting in verbose mode + +Tweak scp's reporting of filenames in verbose mode to be a bit less +confusing with spaces. + +This should be revised to mimic real shell quoting. + +Bug-Ubuntu: https://bugs.launchpad.net/bugs/89945 +Last-Update: 2010-02-27 + +Patch-Name: scp-quoting.patch +--- + scp.c | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/scp.c b/scp.c +index 1adff5cec..df590c4e3 100644 +--- a/scp.c ++++ b/scp.c +@@ -239,8 +239,16 @@ do_local_cmd(arglist *a) + + if (verbose_mode) { + fprintf(stderr, "Executing:"); +- for (i = 0; i < a->num; i++) +- fmprintf(stderr, " %s", a->list[i]); ++ for (i = 0; i < a->num; i++) { ++ if (i == 0) ++ fmprintf(stderr, " %s", a->list[i]); ++ else ++ /* ++ * TODO: misbehaves if a->list[i] contains a ++ * single quote ++ */ ++ fmprintf(stderr, " '%s'", a->list[i]); ++ } + fprintf(stderr, "\n"); + } + if ((pid = fork()) == -1) diff --git a/debian/patches/selinux-role.patch b/debian/patches/selinux-role.patch new file mode 100644 index 0000000..7fb35f2 --- /dev/null +++ b/debian/patches/selinux-role.patch @@ -0,0 +1,471 @@ +From a1b3f6592e7ef61f5d9544fc652ae44f8c47bd2e Mon Sep 17 00:00:00 2001 +From: Manoj Srivastava <srivasta@debian.org> +Date: Sun, 9 Feb 2014 16:09:49 +0000 +Subject: Handle SELinux authorisation roles + +Rejected upstream due to discomfort with magic usernames; a better approach +will need an SSH protocol change. In the meantime, this came from Debian's +SELinux maintainer, so we'll keep it until we have something better. + +Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1641 +Bug-Debian: http://bugs.debian.org/394795 +Last-Update: 2021-11-05 + +Patch-Name: selinux-role.patch +--- + auth.h | 1 + + auth2.c | 10 ++++++++-- + monitor.c | 36 +++++++++++++++++++++++++++++++++--- + monitor.h | 2 ++ + monitor_wrap.c | 27 ++++++++++++++++++++++++--- + monitor_wrap.h | 3 ++- + openbsd-compat/port-linux.c | 21 ++++++++++++++------- + openbsd-compat/port-linux.h | 4 ++-- + platform.c | 4 ++-- + platform.h | 2 +- + session.c | 10 +++++----- + session.h | 2 +- + sshd.c | 2 +- + sshpty.c | 4 ++-- + sshpty.h | 2 +- + 15 files changed, 99 insertions(+), 31 deletions(-) + +diff --git a/auth.h b/auth.h +index 6d2d39762..d16dc66b8 100644 +--- a/auth.h ++++ b/auth.h +@@ -65,6 +65,7 @@ struct Authctxt { + char *service; + struct passwd *pw; /* set if 'valid' */ + char *style; ++ char *role; + + /* Method lists for multiple authentication */ + char **auth_methods; /* modified from server config */ +diff --git a/auth2.c b/auth2.c +index 44bd49177..d66d73876 100644 +--- a/auth2.c ++++ b/auth2.c +@@ -264,7 +264,7 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh) + { + Authctxt *authctxt = ssh->authctxt; + Authmethod *m = NULL; +- char *user = NULL, *service = NULL, *method = NULL, *style = NULL; ++ char *user = NULL, *service = NULL, *method = NULL, *style = NULL, *role = NULL; + int r, authenticated = 0; + double tstart = monotime_double(); + +@@ -278,8 +278,13 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh) + debug("userauth-request for user %s service %s method %s", user, service, method); + debug("attempt %d failures %d", authctxt->attempt, authctxt->failures); + ++ if ((role = strchr(user, '/')) != NULL) ++ *role++ = 0; ++ + if ((style = strchr(user, ':')) != NULL) + *style++ = 0; ++ else if (role && (style = strchr(role, ':')) != NULL) ++ *style++ = '\0'; + + if (authctxt->attempt >= 1024) + auth_maxtries_exceeded(ssh); +@@ -308,8 +313,9 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh) + use_privsep ? " [net]" : ""); + authctxt->service = xstrdup(service); + authctxt->style = style ? xstrdup(style) : NULL; ++ authctxt->role = role ? xstrdup(role) : NULL; + if (use_privsep) +- mm_inform_authserv(service, style); ++ mm_inform_authserv(service, style, role); + userauth_banner(ssh); + if (auth2_setup_methods_lists(authctxt) != 0) + ssh_packet_disconnect(ssh, +diff --git a/monitor.c b/monitor.c +index 5f7df9539..769eda640 100644 +--- a/monitor.c ++++ b/monitor.c +@@ -117,6 +117,7 @@ int mm_answer_sign(struct ssh *, int, struct sshbuf *); + int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *); + int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *); + int mm_answer_authserv(struct ssh *, int, struct sshbuf *); ++int mm_answer_authrole(struct ssh *, int, struct sshbuf *); + int mm_answer_authpassword(struct ssh *, int, struct sshbuf *); + int mm_answer_bsdauthquery(struct ssh *, int, struct sshbuf *); + int mm_answer_bsdauthrespond(struct ssh *, int, struct sshbuf *); +@@ -195,6 +196,7 @@ struct mon_table mon_dispatch_proto20[] = { + {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign}, + {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow}, + {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv}, ++ {MONITOR_REQ_AUTHROLE, MON_ONCE, mm_answer_authrole}, + {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner}, + {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword}, + #ifdef USE_PAM +@@ -815,6 +817,7 @@ mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m) + + /* Allow service/style information on the auth context */ + monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1); ++ monitor_permit(mon_dispatch, MONITOR_REQ_AUTHROLE, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1); + + #ifdef USE_PAM +@@ -848,15 +851,42 @@ mm_answer_authserv(struct ssh *ssh, int sock, struct sshbuf *m) + monitor_permit_authentications(1); + + if ((r = sshbuf_get_cstring(m, &authctxt->service, NULL)) != 0 || +- (r = sshbuf_get_cstring(m, &authctxt->style, NULL)) != 0) ++ (r = sshbuf_get_cstring(m, &authctxt->style, NULL)) != 0 || ++ (r = sshbuf_get_cstring(m, &authctxt->role, NULL)) != 0) + fatal_fr(r, "parse"); +- debug3_f("service=%s, style=%s", authctxt->service, authctxt->style); ++ debug3_f("service=%s, style=%s, role=%s", ++ authctxt->service, authctxt->style, authctxt->role); + + if (strlen(authctxt->style) == 0) { + free(authctxt->style); + authctxt->style = NULL; + } + ++ if (strlen(authctxt->role) == 0) { ++ free(authctxt->role); ++ authctxt->role = NULL; ++ } ++ ++ return (0); ++} ++ ++int ++mm_answer_authrole(struct ssh *ssh, int sock, struct sshbuf *m) ++{ ++ int r; ++ ++ monitor_permit_authentications(1); ++ ++ if ((r = sshbuf_get_cstring(m, &authctxt->role, NULL)) != 0) ++ fatal("%s: buffer error: %s", __func__, ssh_err(r)); ++ debug3("%s: role=%s", ++ __func__, authctxt->role); ++ ++ if (strlen(authctxt->role) == 0) { ++ free(authctxt->role); ++ authctxt->role = NULL; ++ } ++ + return (0); + } + +@@ -1578,7 +1608,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m) + res = pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty)); + if (res == 0) + goto error; +- pty_setowner(authctxt->pw, s->tty); ++ pty_setowner(authctxt->pw, s->tty, authctxt->role); + + if ((r = sshbuf_put_u32(m, 1)) != 0 || + (r = sshbuf_put_cstring(m, s->tty)) != 0) +diff --git a/monitor.h b/monitor.h +index 2b1a2d590..4d87284aa 100644 +--- a/monitor.h ++++ b/monitor.h +@@ -65,6 +65,8 @@ enum monitor_reqtype { + + MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151, + MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153, ++ ++ MONITOR_REQ_AUTHROLE = 154, + }; + + struct ssh; +diff --git a/monitor_wrap.c b/monitor_wrap.c +index a4b66c56f..6fb47becb 100644 +--- a/monitor_wrap.c ++++ b/monitor_wrap.c +@@ -375,10 +375,10 @@ mm_auth2_read_banner(void) + return (banner); + } + +-/* Inform the privileged process about service and style */ ++/* Inform the privileged process about service, style, and role */ + + void +-mm_inform_authserv(char *service, char *style) ++mm_inform_authserv(char *service, char *style, char *role) + { + struct sshbuf *m; + int r; +@@ -388,7 +388,8 @@ mm_inform_authserv(char *service, char *style) + if ((m = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); + if ((r = sshbuf_put_cstring(m, service)) != 0 || +- (r = sshbuf_put_cstring(m, style ? style : "")) != 0) ++ (r = sshbuf_put_cstring(m, style ? style : "")) != 0 || ++ (r = sshbuf_put_cstring(m, role ? role : "")) != 0) + fatal_fr(r, "assemble"); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHSERV, m); +@@ -396,6 +397,26 @@ mm_inform_authserv(char *service, char *style) + sshbuf_free(m); + } + ++/* Inform the privileged process about role */ ++ ++void ++mm_inform_authrole(char *role) ++{ ++ struct sshbuf *m; ++ int r; ++ ++ debug3("%s entering", __func__); ++ ++ if ((m = sshbuf_new()) == NULL) ++ fatal("%s: sshbuf_new failed", __func__); ++ if ((r = sshbuf_put_cstring(m, role ? role : "")) != 0) ++ fatal("%s: buffer error: %s", __func__, ssh_err(r)); ++ ++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHROLE, m); ++ ++ sshbuf_free(m); ++} ++ + /* Do the password authentication */ + int + mm_auth_password(struct ssh *ssh, char *password) +diff --git a/monitor_wrap.h b/monitor_wrap.h +index 830fdb308..c84f96d0c 100644 +--- a/monitor_wrap.h ++++ b/monitor_wrap.h +@@ -48,7 +48,8 @@ DH *mm_choose_dh(int, int, int); + int mm_sshkey_sign(struct ssh *, struct sshkey *, u_char **, size_t *, + const u_char *, size_t, const char *, const char *, + const char *, u_int compat); +-void mm_inform_authserv(char *, char *); ++void mm_inform_authserv(char *, char *, char *); ++void mm_inform_authrole(char *); + struct passwd *mm_getpwnamallow(struct ssh *, const char *); + char *mm_auth2_read_banner(void); + int mm_auth_password(struct ssh *, char *); +diff --git a/openbsd-compat/port-linux.c b/openbsd-compat/port-linux.c +index 77cb8213a..3a59ba1b7 100644 +--- a/openbsd-compat/port-linux.c ++++ b/openbsd-compat/port-linux.c +@@ -56,7 +56,7 @@ ssh_selinux_enabled(void) + + /* Return the default security context for the given username */ + static char * +-ssh_selinux_getctxbyname(char *pwname) ++ssh_selinux_getctxbyname(char *pwname, const char *role) + { + char *sc = NULL, *sename = NULL, *lvl = NULL; + int r; +@@ -70,9 +70,16 @@ ssh_selinux_getctxbyname(char *pwname) + #endif + + #ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL +- r = get_default_context_with_level(sename, lvl, NULL, &sc); ++ if (role != NULL && role[0]) ++ r = get_default_context_with_rolelevel(sename, role, lvl, NULL, ++ &sc); ++ else ++ r = get_default_context_with_level(sename, lvl, NULL, &sc); + #else +- r = get_default_context(sename, NULL, &sc); ++ if (role != NULL && role[0]) ++ r = get_default_context_with_role(sename, role, NULL, &sc); ++ else ++ r = get_default_context(sename, NULL, &sc); + #endif + + if (r != 0) { +@@ -102,7 +109,7 @@ ssh_selinux_getctxbyname(char *pwname) + + /* Set the execution context to the default for the specified user */ + void +-ssh_selinux_setup_exec_context(char *pwname) ++ssh_selinux_setup_exec_context(char *pwname, const char *role) + { + char *user_ctx = NULL; + +@@ -111,7 +118,7 @@ ssh_selinux_setup_exec_context(char *pwname) + + debug3("%s: setting execution context", __func__); + +- user_ctx = ssh_selinux_getctxbyname(pwname); ++ user_ctx = ssh_selinux_getctxbyname(pwname, role); + if (setexeccon(user_ctx) != 0) { + switch (security_getenforce()) { + case -1: +@@ -133,7 +140,7 @@ ssh_selinux_setup_exec_context(char *pwname) + + /* Set the TTY context for the specified user */ + void +-ssh_selinux_setup_pty(char *pwname, const char *tty) ++ssh_selinux_setup_pty(char *pwname, const char *tty, const char *role) + { + char *new_tty_ctx = NULL, *user_ctx = NULL, *old_tty_ctx = NULL; + security_class_t chrclass; +@@ -143,7 +150,7 @@ ssh_selinux_setup_pty(char *pwname, const char *tty) + + debug3("%s: setting TTY context on %s", __func__, tty); + +- user_ctx = ssh_selinux_getctxbyname(pwname); ++ user_ctx = ssh_selinux_getctxbyname(pwname, role); + + /* XXX: should these calls fatal() upon failure in enforcing mode? */ + +diff --git a/openbsd-compat/port-linux.h b/openbsd-compat/port-linux.h +index 3c22a854d..c88129428 100644 +--- a/openbsd-compat/port-linux.h ++++ b/openbsd-compat/port-linux.h +@@ -19,8 +19,8 @@ + + #ifdef WITH_SELINUX + int ssh_selinux_enabled(void); +-void ssh_selinux_setup_pty(char *, const char *); +-void ssh_selinux_setup_exec_context(char *); ++void ssh_selinux_setup_pty(char *, const char *, const char *); ++void ssh_selinux_setup_exec_context(char *, const char *); + void ssh_selinux_change_context(const char *); + void ssh_selinux_setfscreatecon(const char *); + #endif +diff --git a/platform.c b/platform.c +index 4fe8744ee..70c3a9b58 100644 +--- a/platform.c ++++ b/platform.c +@@ -144,7 +144,7 @@ platform_setusercontext(struct passwd *pw) + * called if sshd is running as root. + */ + void +-platform_setusercontext_post_groups(struct passwd *pw) ++platform_setusercontext_post_groups(struct passwd *pw, const char *role) + { + #if !defined(HAVE_LOGIN_CAP) && defined(USE_PAM) + /* +@@ -185,7 +185,7 @@ platform_setusercontext_post_groups(struct passwd *pw) + } + #endif /* HAVE_SETPCRED */ + #ifdef WITH_SELINUX +- ssh_selinux_setup_exec_context(pw->pw_name); ++ ssh_selinux_setup_exec_context(pw->pw_name, role); + #endif + } + +diff --git a/platform.h b/platform.h +index 7fef8c983..027fdfb51 100644 +--- a/platform.h ++++ b/platform.h +@@ -25,7 +25,7 @@ void platform_post_fork_parent(pid_t child_pid); + void platform_post_fork_child(void); + int platform_privileged_uidswap(void); + void platform_setusercontext(struct passwd *); +-void platform_setusercontext_post_groups(struct passwd *); ++void platform_setusercontext_post_groups(struct passwd *, const char *); + char *platform_get_krb5_client(const char *); + char *platform_krb5_get_principal_name(const char *); + int platform_locked_account(struct passwd *); +diff --git a/session.c b/session.c +index 96960f2da..492463529 100644 +--- a/session.c ++++ b/session.c +@@ -1355,7 +1355,7 @@ safely_chroot(const char *path, uid_t uid) + + /* Set login name, uid, gid, and groups. */ + void +-do_setusercontext(struct passwd *pw) ++do_setusercontext(struct passwd *pw, const char *role) + { + char uidstr[32], *chroot_path, *tmp; + +@@ -1383,7 +1383,7 @@ do_setusercontext(struct passwd *pw) + endgrent(); + #endif + +- platform_setusercontext_post_groups(pw); ++ platform_setusercontext_post_groups(pw, role); + + if (!in_chroot && options.chroot_directory != NULL && + strcasecmp(options.chroot_directory, "none") != 0) { +@@ -1527,7 +1527,7 @@ do_child(struct ssh *ssh, Session *s, const char *command) + + /* Force a password change */ + if (s->authctxt->force_pwchange) { +- do_setusercontext(pw); ++ do_setusercontext(pw, s->authctxt->role); + child_close_fds(ssh); + do_pwchange(s); + exit(1); +@@ -1545,7 +1545,7 @@ do_child(struct ssh *ssh, Session *s, const char *command) + /* When PAM is enabled we rely on it to do the nologin check */ + if (!options.use_pam) + do_nologin(pw); +- do_setusercontext(pw); ++ do_setusercontext(pw, s->authctxt->role); + /* + * PAM session modules in do_setusercontext may have + * generated messages, so if this in an interactive +@@ -1941,7 +1941,7 @@ session_pty_req(struct ssh *ssh, Session *s) + sshpkt_fatal(ssh, r, "%s: parse packet", __func__); + + if (!use_privsep) +- pty_setowner(s->pw, s->tty); ++ pty_setowner(s->pw, s->tty, s->authctxt->role); + + /* Set window size from the packet. */ + pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel); +diff --git a/session.h b/session.h +index 344a1ddf9..20ea822a7 100644 +--- a/session.h ++++ b/session.h +@@ -77,7 +77,7 @@ void session_pty_cleanup2(Session *); + Session *session_new(void); + Session *session_by_tty(char *); + void session_close(struct ssh *, Session *); +-void do_setusercontext(struct passwd *); ++void do_setusercontext(struct passwd *, const char *); + + const char *session_get_remote_name_or_ip(struct ssh *, u_int, int); + +diff --git a/sshd.c b/sshd.c +index 7944cf633..dd4a19131 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -584,7 +584,7 @@ privsep_postauth(struct ssh *ssh, Authctxt *authctxt) + reseed_prngs(); + + /* Drop privileges */ +- do_setusercontext(authctxt->pw); ++ do_setusercontext(authctxt->pw, authctxt->role); + + skip: + /* It is safe now to apply the key state */ +diff --git a/sshpty.c b/sshpty.c +index cae0b977a..7870c6482 100644 +--- a/sshpty.c ++++ b/sshpty.c +@@ -163,7 +163,7 @@ pty_change_window_size(int ptyfd, u_int row, u_int col, + } + + void +-pty_setowner(struct passwd *pw, const char *tty) ++pty_setowner(struct passwd *pw, const char *tty, const char *role) + { + struct group *grp; + gid_t gid; +@@ -187,7 +187,7 @@ pty_setowner(struct passwd *pw, const char *tty) + strerror(errno)); + + #ifdef WITH_SELINUX +- ssh_selinux_setup_pty(pw->pw_name, tty); ++ ssh_selinux_setup_pty(pw->pw_name, tty, role); + #endif + + if (st.st_uid != pw->pw_uid || st.st_gid != gid) { +diff --git a/sshpty.h b/sshpty.h +index 9ec7e9a15..de7e000ae 100644 +--- a/sshpty.h ++++ b/sshpty.h +@@ -24,5 +24,5 @@ int pty_allocate(int *, int *, char *, size_t); + void pty_release(const char *); + void pty_make_controlling_tty(int *, const char *); + void pty_change_window_size(int, u_int, u_int, u_int, u_int); +-void pty_setowner(struct passwd *, const char *); ++void pty_setowner(struct passwd *, const char *, const char *); + void disconnect_controlling_tty(void); diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..f9d3791 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1,35 @@ +gssapi.patch +restore-tcp-wrappers.patch +selinux-role.patch +ssh-vulnkey-compat.patch +keepalive-extensions.patch +syslog-level-silent.patch +user-group-modes.patch +scp-quoting.patch +shell-path.patch +dnssec-sshfp.patch +mention-ssh-keygen-on-keychange.patch +package-versioning.patch +debian-banner.patch +authorized-keys-man-symlink.patch +openbsd-docs.patch +ssh-argv0.patch +doc-hash-tab-completion.patch +ssh-agent-setgid.patch +no-openssl-version-status.patch +gnome-ssh-askpass2-icon.patch +systemd-readiness.patch +debian-config.patch +restore-authorized_keys2.patch +revert-ipqos-defaults.patch +maxhostnamelen.patch +conch-ssh-rsa.patch +systemd-socket-activation.patch +remove-spurious-ssh-agent-options.patch +CVE-2023-38408-1.patch +CVE-2023-38408-2.patch +CVE-2023-38408-3.patch +CVE-2023-28531.patch +CVE-2023-48795.patch +CVE-2023-51384.patch +CVE-2023-51385.patch diff --git a/debian/patches/shell-path.patch b/debian/patches/shell-path.patch new file mode 100644 index 0000000..5f839df --- /dev/null +++ b/debian/patches/shell-path.patch @@ -0,0 +1,39 @@ +From b364a18c85a959fdfd0a5a2c497482809cadf29f Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Sun, 9 Feb 2014 16:10:00 +0000 +Subject: Look for $SHELL on the path for ProxyCommand/LocalCommand + +There's some debate on the upstream bug about whether POSIX requires this. +I (Colin Watson) agree with Vincent and think it does. + +Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1494 +Bug-Debian: http://bugs.debian.org/492728 +Last-Update: 2020-02-21 + +Patch-Name: shell-path.patch +--- + sshconnect.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/sshconnect.c b/sshconnect.c +index 792bc34bc..891e968dc 100644 +--- a/sshconnect.c ++++ b/sshconnect.c +@@ -248,7 +248,7 @@ ssh_proxy_connect(struct ssh *ssh, const char *host, const char *host_arg, + * extra privileges above. + */ + ssh_signal(SIGPIPE, SIG_DFL); +- execv(argv[0], argv); ++ execvp(argv[0], argv); + perror(argv[0]); + exit(1); + } +@@ -1671,7 +1671,7 @@ ssh_local_cmd(const char *args) + if (pid == 0) { + ssh_signal(SIGPIPE, SIG_DFL); + debug3("Executing %s -c \"%s\"", shell, args); +- execl(shell, shell, "-c", args, (char *)NULL); ++ execlp(shell, shell, "-c", args, (char *)NULL); + error("Couldn't execute %s -c \"%s\": %s", + shell, args, strerror(errno)); + _exit(1); diff --git a/debian/patches/ssh-agent-setgid.patch b/debian/patches/ssh-agent-setgid.patch new file mode 100644 index 0000000..14478d3 --- /dev/null +++ b/debian/patches/ssh-agent-setgid.patch @@ -0,0 +1,40 @@ +From bf54d67a00bf4d408f0e52236c4248cecfb5177f Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Sun, 9 Feb 2014 16:10:13 +0000 +Subject: Document consequences of ssh-agent being setgid in ssh-agent(1) + +Bug-Debian: http://bugs.debian.org/711623 +Forwarded: no +Last-Update: 2020-02-21 + +Patch-Name: ssh-agent-setgid.patch +--- + ssh-agent.1 | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/ssh-agent.1 b/ssh-agent.1 +index b0bf65da8..ba418bb41 100644 +--- a/ssh-agent.1 ++++ b/ssh-agent.1 +@@ -230,6 +230,21 @@ socket and stores its pathname in this variable. + It is accessible only to the current user, + but is easily abused by root or another instance of the same user. + .El ++.Pp ++In Debian, ++.Nm ++is installed with the set-group-id bit set, to prevent ++.Xr ptrace 2 ++attacks retrieving private key material. ++This has the side-effect of causing the run-time linker to remove certain ++environment variables which might have security implications for set-id ++programs, including ++.Ev LD_PRELOAD , ++.Ev LD_LIBRARY_PATH , ++and ++.Ev TMPDIR . ++If you need to set any of these environment variables, you will need to do ++so in the program executed by ssh-agent. + .Sh FILES + .Bl -tag -width Ds + .It Pa $TMPDIR/ssh-XXXXXXXXXX/agent.<ppid> diff --git a/debian/patches/ssh-argv0.patch b/debian/patches/ssh-argv0.patch new file mode 100644 index 0000000..756d80e --- /dev/null +++ b/debian/patches/ssh-argv0.patch @@ -0,0 +1,31 @@ +From b252064f6d116feca5d07dfe6dfd62ba005927bd Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Sun, 9 Feb 2014 16:10:10 +0000 +Subject: ssh(1): Refer to ssh-argv0(1) + +Old versions of OpenSSH (up to 2.5 or thereabouts) allowed creating symlinks +to ssh with the name of the host you want to connect to. Debian ships an +ssh-argv0 script restoring this feature; this patch refers to its manual +page from ssh(1). + +Bug-Debian: http://bugs.debian.org/111341 +Forwarded: not-needed +Last-Update: 2013-09-14 + +Patch-Name: ssh-argv0.patch +--- + ssh.1 | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/ssh.1 b/ssh.1 +index 6b56f5638..d07cd7840 100644 +--- a/ssh.1 ++++ b/ssh.1 +@@ -1667,6 +1667,7 @@ if an error occurred. + .Xr sftp 1 , + .Xr ssh-add 1 , + .Xr ssh-agent 1 , ++.Xr ssh-argv0 1 , + .Xr ssh-keygen 1 , + .Xr ssh-keyscan 1 , + .Xr tun 4 , diff --git a/debian/patches/ssh-vulnkey-compat.patch b/debian/patches/ssh-vulnkey-compat.patch new file mode 100644 index 0000000..f37f827 --- /dev/null +++ b/debian/patches/ssh-vulnkey-compat.patch @@ -0,0 +1,42 @@ +From 3878210a9526dc6c78c48d959bab0afb0052b64f Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@ubuntu.com> +Date: Sun, 9 Feb 2014 16:09:50 +0000 +Subject: Accept obsolete ssh-vulnkey configuration options + +These options were used as part of Debian's response to CVE-2008-0166. +Nearly six years later, we no longer need to continue carrying the bulk +of that patch, but we do need to avoid failing when the associated +configuration options are still present. + +Last-Update: 2014-02-09 + +Patch-Name: ssh-vulnkey-compat.patch +--- + readconf.c | 1 + + servconf.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/readconf.c b/readconf.c +index 55af0115d..4a39f5b66 100644 +--- a/readconf.c ++++ b/readconf.c +@@ -194,6 +194,7 @@ static struct { + { "fallbacktorsh", oDeprecated }, + { "globalknownhostsfile2", oDeprecated }, + { "rhostsauthentication", oDeprecated }, ++ { "useblacklistedkeys", oDeprecated }, + { "userknownhostsfile2", oDeprecated }, + { "useroaming", oDeprecated }, + { "usersh", oDeprecated }, +diff --git a/servconf.c b/servconf.c +index 940de9e15..6529f2736 100644 +--- a/servconf.c ++++ b/servconf.c +@@ -651,6 +651,7 @@ static struct { + { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL }, + { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL }, + { "strictmodes", sStrictModes, SSHCFG_GLOBAL }, ++ { "permitblacklistedkeys", sDeprecated, SSHCFG_GLOBAL }, + { "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL }, + { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL }, + { "uselogin", sDeprecated, SSHCFG_GLOBAL }, diff --git a/debian/patches/syslog-level-silent.patch b/debian/patches/syslog-level-silent.patch new file mode 100644 index 0000000..e1a8aee --- /dev/null +++ b/debian/patches/syslog-level-silent.patch @@ -0,0 +1,47 @@ +From ac80435d753ff39d9c6ded2f7535d770f257fc59 Mon Sep 17 00:00:00 2001 +From: Natalie Amery <nmamery@chiark.greenend.org.uk> +Date: Sun, 9 Feb 2014 16:09:54 +0000 +Subject: "LogLevel SILENT" compatibility + +"LogLevel SILENT" (-qq) was introduced in Debian openssh 1:3.0.1p1-1 to +match the behaviour of non-free SSH, in which -q does not suppress fatal +errors. However, this was unintentionally broken in 1:4.6p1-2 and nobody +complained, so we've dropped most of it. The parts that remain are basic +configuration file compatibility, and an adjustment to "Pseudo-terminal will +not be allocated ..." which should be split out into a separate patch. + +Author: Matthew Vernon <matthew@debian.org> +Author: Colin Watson <cjwatson@debian.org> +Last-Update: 2013-09-14 + +Patch-Name: syslog-level-silent.patch +--- + log.c | 1 + + ssh.c | 2 +- + 2 files changed, 2 insertions(+), 1 deletion(-) + +diff --git a/log.c b/log.c +index 99bf046a7..bdc4b6515 100644 +--- a/log.c ++++ b/log.c +@@ -96,6 +96,7 @@ static struct { + LogLevel val; + } log_levels[] = + { ++ { "SILENT", SYSLOG_LEVEL_QUIET }, /* compatibility */ + { "QUIET", SYSLOG_LEVEL_QUIET }, + { "FATAL", SYSLOG_LEVEL_FATAL }, + { "ERROR", SYSLOG_LEVEL_ERROR }, +diff --git a/ssh.c b/ssh.c +index 855777d7f..8b6b54558 100644 +--- a/ssh.c ++++ b/ssh.c +@@ -1363,7 +1363,7 @@ main(int ac, char **av) + /* Do not allocate a tty if stdin is not a tty. */ + if ((!isatty(fileno(stdin)) || options.stdin_null) && + options.request_tty != REQUEST_TTY_FORCE) { +- if (tty_flag) ++ if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET) + logit("Pseudo-terminal will not be allocated because " + "stdin is not a terminal."); + tty_flag = 0; diff --git a/debian/patches/systemd-readiness.patch b/debian/patches/systemd-readiness.patch new file mode 100644 index 0000000..8f9f543 --- /dev/null +++ b/debian/patches/systemd-readiness.patch @@ -0,0 +1,84 @@ +From 5d04f3ebd2825c03fa7c39e27c28bf3384345806 Mon Sep 17 00:00:00 2001 +From: Michael Biebl <biebl@debian.org> +Date: Mon, 21 Dec 2015 16:08:47 +0000 +Subject: Add systemd readiness notification support + +Bug-Debian: https://bugs.debian.org/778913 +Forwarded: no +Last-Update: 2017-08-22 + +Patch-Name: systemd-readiness.patch +--- + configure.ac | 24 ++++++++++++++++++++++++ + sshd.c | 9 +++++++++ + 2 files changed, 33 insertions(+) + +diff --git a/configure.ac b/configure.ac +index 0c59c8b50..23423e48d 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -4915,6 +4915,29 @@ AC_SUBST([GSSLIBS]) + AC_SUBST([K5LIBS]) + AC_SUBST([CHANNELLIBS]) + ++# Check whether user wants systemd support ++SYSTEMD_MSG="no" ++AC_ARG_WITH(systemd, ++ [ --with-systemd Enable systemd support], ++ [ if test "x$withval" != "xno" ; then ++ AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no]) ++ if test "$PKGCONFIG" != "no"; then ++ AC_MSG_CHECKING([for libsystemd]) ++ if $PKGCONFIG --exists libsystemd; then ++ SYSTEMD_CFLAGS=`$PKGCONFIG --cflags libsystemd` ++ SYSTEMD_LIBS=`$PKGCONFIG --libs libsystemd` ++ CPPFLAGS="$CPPFLAGS $SYSTEMD_CFLAGS" ++ SSHDLIBS="$SSHDLIBS $SYSTEMD_LIBS" ++ AC_MSG_RESULT([yes]) ++ AC_DEFINE(HAVE_SYSTEMD, 1, [Define if you want systemd support.]) ++ SYSTEMD_MSG="yes" ++ else ++ AC_MSG_RESULT([no]) ++ fi ++ fi ++ fi ] ++) ++ + # Looking for programs, paths and files + + PRIVSEP_PATH=/var/empty +@@ -5715,6 +5738,7 @@ echo " libldns support: $LDNS_MSG" + echo " Solaris process contract support: $SPC_MSG" + echo " Solaris project support: $SP_MSG" + echo " Solaris privilege support: $SPP_MSG" ++echo " systemd support: $SYSTEMD_MSG" + echo " IP address in \$DISPLAY hack: $DISPLAY_HACK_MSG" + echo " Translate v4 in v6 hack: $IPV4_IN6_HACK_MSG" + echo " BSD Auth support: $BSD_AUTH_MSG" +diff --git a/sshd.c b/sshd.c +index b4ee435e5..0a4eefe01 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -88,6 +88,10 @@ + #include <prot.h> + #endif + ++#ifdef HAVE_SYSTEMD ++#include <systemd/sd-daemon.h> ++#endif ++ + #include "xmalloc.h" + #include "ssh.h" + #include "ssh2.h" +@@ -2094,6 +2098,11 @@ main(int ac, char **av) + } + } + ++#ifdef HAVE_SYSTEMD ++ /* Signal systemd that we are ready to accept connections */ ++ sd_notify(0, "READY=1"); ++#endif ++ + /* Accept a connection and return in a forked child */ + server_accept_loop(&sock_in, &sock_out, + &newsock, config_s); diff --git a/debian/patches/systemd-socket-activation.patch b/debian/patches/systemd-socket-activation.patch new file mode 100644 index 0000000..5441622 --- /dev/null +++ b/debian/patches/systemd-socket-activation.patch @@ -0,0 +1,138 @@ +From 4cedd1c9acac0fba598db2eaf43278dfe8e53ef0 Mon Sep 17 00:00:00 2001 +From: Steve Langasek <steve.langasek@ubuntu.com> +Date: Thu, 1 Sep 2022 16:03:37 +0100 +Subject: Support systemd socket activation + +Unlike inetd socket activation, with systemd socket activation the +supervisor passes the listened-on socket to the child process and lets +the child process handle the accept(). This lets us do delayed start +of the sshd daemon without becoming incompatible with config options +like ClientAliveCountMax. + +Last-Update: 2022-09-01 + +Patch-Name: systemd-socket-activation.patch +--- + sshd.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 75 insertions(+), 14 deletions(-) + +diff --git a/sshd.c b/sshd.c +index 0a4eefe01..fc22fcb62 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -141,10 +141,16 @@ int deny_severity; + #endif /* LIBWRAP */ + + /* Re-exec fds */ +-#define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) +-#define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2) +-#define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 3) +-#define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4) ++#ifdef HAVE_SYSTEMD ++#define SYSTEMD_OFFSET sd_listen_fds(0) ++#else ++#define SYSTEMD_OFFSET 0 ++#endif ++ ++#define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1 + SYSTEMD_OFFSET) ++#define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2 + SYSTEMD_OFFSET) ++#define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 3 + SYSTEMD_OFFSET) ++#define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4 + SYSTEMD_OFFSET) + + extern char *__progname; + +@@ -1025,6 +1031,48 @@ server_accept_inetd(int *sock_in, int *sock_out) + debug("inetd sockets after dupping: %d, %d", *sock_in, *sock_out); + } + ++#ifdef HAVE_SYSTEMD ++/* ++ * Configure our socket fds that were passed from systemd ++ */ ++static void ++setup_systemd_socket(int listen_sock) ++{ ++ int ret; ++ struct sockaddr_storage addr; ++ socklen_t len = sizeof(addr); ++ char ntop[NI_MAXHOST], strport[NI_MAXSERV]; ++ ++ if (getsockname(listen_sock, (struct sockaddr *)&addr, &len) != 0) ++ return; ++ ++ if (((struct sockaddr *)&addr)->sa_family != AF_INET ++ && ((struct sockaddr *)&addr)->sa_family != AF_INET6) ++ return; ++ if (num_listen_socks >= MAX_LISTEN_SOCKS) ++ fatal("Too many listen sockets. " ++ "Enlarge MAX_LISTEN_SOCKS"); ++ if ((ret = getnameinfo((struct sockaddr *)&addr, len, ntop, ++ sizeof(ntop), strport, sizeof(strport), ++ NI_NUMERICHOST|NI_NUMERICSERV)) != 0) { ++ error("getnameinfo failed: %.100s", ++ ssh_gai_strerror(ret)); ++ return; ++ } ++ if (set_nonblock(listen_sock) == -1) { ++ close(listen_sock); ++ return; ++ } ++ /* Socket options */ ++ set_reuseaddr(listen_sock); ++ ++ listen_socks[num_listen_socks] = listen_sock; ++ num_listen_socks++; ++ ++ logit("Server listening on %s port %s.", ntop, strport); ++} ++#endif ++ + /* + * Listen for TCP connections + */ +@@ -1104,22 +1152,35 @@ static void + server_listen(void) + { + u_int i; ++#ifdef HAVE_SYSTEMD ++ int systemd_socket_count; ++#endif + + /* Initialise per-source limit tracking. */ + srclimit_init(options.max_startups, options.per_source_max_startups, + options.per_source_masklen_ipv4, options.per_source_masklen_ipv6); + +- for (i = 0; i < options.num_listen_addrs; i++) { +- listen_on_addrs(&options.listen_addrs[i]); +- freeaddrinfo(options.listen_addrs[i].addrs); +- free(options.listen_addrs[i].rdomain); +- memset(&options.listen_addrs[i], 0, +- sizeof(options.listen_addrs[i])); ++#ifdef HAVE_SYSTEMD ++ systemd_socket_count = sd_listen_fds(0); ++ if (systemd_socket_count > 0) ++ { ++ int i; ++ for (i = 0; i < systemd_socket_count; i++) ++ setup_systemd_socket(SD_LISTEN_FDS_START + i); ++ } else ++#endif ++ { ++ for (i = 0; i < options.num_listen_addrs; i++) { ++ listen_on_addrs(&options.listen_addrs[i]); ++ freeaddrinfo(options.listen_addrs[i].addrs); ++ free(options.listen_addrs[i].rdomain); ++ memset(&options.listen_addrs[i], 0, ++ sizeof(options.listen_addrs[i])); ++ } ++ free(options.listen_addrs); ++ options.listen_addrs = NULL; ++ options.num_listen_addrs = 0; + } +- free(options.listen_addrs); +- options.listen_addrs = NULL; +- options.num_listen_addrs = 0; +- + if (!num_listen_socks) + fatal("Cannot bind any address."); + } diff --git a/debian/patches/user-group-modes.patch b/debian/patches/user-group-modes.patch new file mode 100644 index 0000000..51a233b --- /dev/null +++ b/debian/patches/user-group-modes.patch @@ -0,0 +1,210 @@ +From ad9efda53c54f37dbd429c16db4be2946f27063e Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@debian.org> +Date: Sun, 9 Feb 2014 16:09:58 +0000 +Subject: Allow harmless group-writability + +Allow secure files (~/.ssh/config, ~/.ssh/authorized_keys, etc.) to be +group-writable, provided that the group in question contains only the file's +owner. Rejected upstream for IMO incorrect reasons (e.g. a misunderstanding +about the contents of gr->gr_mem). Given that per-user groups and umask 002 +are the default setup in Debian (for good reasons - this makes operating in +setgid directories with other groups much easier), we need to permit this by +default. + +Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1060 +Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=314347 +Last-Update: 2022-02-23 + +Patch-Name: user-group-modes.patch +--- + auth-rhosts.c | 6 ++---- + auth.c | 3 +-- + misc.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++----- + misc.h | 2 ++ + readconf.c | 3 +-- + ssh.1 | 2 ++ + ssh_config.5 | 2 ++ + 7 files changed, 62 insertions(+), 13 deletions(-) + +diff --git a/auth-rhosts.c b/auth-rhosts.c +index 56724677a..e15f5bc5a 100644 +--- a/auth-rhosts.c ++++ b/auth-rhosts.c +@@ -266,8 +266,7 @@ auth_rhosts2(struct passwd *pw, const char *client_user, const char *hostname, + return 0; + } + if (options.strict_modes && +- ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || +- (st.st_mode & 022) != 0)) { ++ !secure_permissions(&st, pw->pw_uid)) { + logit("Rhosts authentication refused for %.100s: " + "bad ownership or modes for home directory.", pw->pw_name); + auth_debug_add("Rhosts authentication refused for %.100s: " +@@ -296,8 +295,7 @@ auth_rhosts2(struct passwd *pw, const char *client_user, const char *hostname, + * allowing access to their account by anyone. + */ + if (options.strict_modes && +- ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || +- (st.st_mode & 022) != 0)) { ++ !secure_permissions(&st, pw->pw_uid)) { + logit("Rhosts authentication refused for %.100s: " + "bad modes for %.200s", pw->pw_name, path); + auth_debug_add("Bad file modes for %.200s", path); +diff --git a/auth.c b/auth.c +index 946dc6074..6653b9e15 100644 +--- a/auth.c ++++ b/auth.c +@@ -432,8 +432,7 @@ check_key_in_hostfiles(struct passwd *pw, struct sshkey *key, const char *host, + user_hostfile = tilde_expand_filename(userfile, pw->pw_uid); + if (options.strict_modes && + (stat(user_hostfile, &st) == 0) && +- ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || +- (st.st_mode & 022) != 0)) { ++ !secure_permissions(&st, pw->pw_uid)) { + logit("Authentication refused for %.100s: " + "bad owner or modes for %.200s", + pw->pw_name, user_hostfile); +diff --git a/misc.c b/misc.c +index c098dc610..2268d8875 100644 +--- a/misc.c ++++ b/misc.c +@@ -55,9 +55,9 @@ + #include <netdb.h> + #ifdef HAVE_PATHS_H + # include <paths.h> ++#endif + #include <pwd.h> + #include <grp.h> +-#endif + #ifdef SSH_TUN_OPENBSD + #include <net/if.h> + #endif +@@ -1385,6 +1385,55 @@ percent_dollar_expand(const char *string, ...) + return ret; + } + ++int ++secure_permissions(struct stat *st, uid_t uid) ++{ ++ if (!platform_sys_dir_uid(st->st_uid) && st->st_uid != uid) ++ return 0; ++ if ((st->st_mode & 002) != 0) ++ return 0; ++ if ((st->st_mode & 020) != 0) { ++ /* If the file is group-writable, the group in question must ++ * have exactly one member, namely the file's owner. ++ * (Zero-member groups are typically used by setgid ++ * binaries, and are unlikely to be suitable.) ++ */ ++ struct passwd *pw; ++ struct group *gr; ++ int members = 0; ++ ++ gr = getgrgid(st->st_gid); ++ if (!gr) ++ return 0; ++ ++ /* Check primary group memberships. */ ++ while ((pw = getpwent()) != NULL) { ++ if (pw->pw_gid == gr->gr_gid) { ++ ++members; ++ if (pw->pw_uid != uid) ++ return 0; ++ } ++ } ++ endpwent(); ++ ++ pw = getpwuid(st->st_uid); ++ if (!pw) ++ return 0; ++ ++ /* Check supplementary group memberships. */ ++ if (gr->gr_mem[0]) { ++ ++members; ++ if (strcmp(pw->pw_name, gr->gr_mem[0]) || ++ gr->gr_mem[1]) ++ return 0; ++ } ++ ++ if (!members) ++ return 0; ++ } ++ return 1; ++} ++ + int + tun_open(int tun, int mode, char **ifname) + { +@@ -2194,8 +2243,7 @@ safe_path(const char *name, struct stat *stp, const char *pw_dir, + snprintf(err, errlen, "%s is not a regular file", buf); + return -1; + } +- if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || +- (stp->st_mode & 022) != 0) { ++ if (!secure_permissions(stp, uid)) { + snprintf(err, errlen, "bad ownership or modes for file %s", + buf); + return -1; +@@ -2210,8 +2258,7 @@ safe_path(const char *name, struct stat *stp, const char *pw_dir, + strlcpy(buf, cp, sizeof(buf)); + + if (stat(buf, &st) == -1 || +- (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || +- (st.st_mode & 022) != 0) { ++ !secure_permissions(&st, uid)) { + snprintf(err, errlen, + "bad ownership or modes for directory %s", buf); + return -1; +diff --git a/misc.h b/misc.h +index 84d93e059..31b6557c6 100644 +--- a/misc.h ++++ b/misc.h +@@ -233,6 +233,8 @@ struct notifier_ctx *notify_start(int, const char *, ...) + void notify_complete(struct notifier_ctx *, const char *, ...) + __attribute__((format(printf, 2, 3))); + ++int secure_permissions(struct stat *st, uid_t uid); ++ + #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) + #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) + #define ROUNDUP(x, y) ((((x)+((y)-1))/(y))*(y)) +diff --git a/readconf.c b/readconf.c +index 9ac71133a..18e814039 100644 +--- a/readconf.c ++++ b/readconf.c +@@ -2319,8 +2319,7 @@ read_config_file_depth(const char *filename, struct passwd *pw, + + if (fstat(fileno(f), &sb) == -1) + fatal("fstat %s: %s", filename, strerror(errno)); +- if (((sb.st_uid != 0 && sb.st_uid != getuid()) || +- (sb.st_mode & 022) != 0)) ++ if (!secure_permissions(&sb, getuid())) + fatal("Bad owner or permissions on %s", filename); + } + +diff --git a/ssh.1 b/ssh.1 +index dfd80b8bc..4028f971a 100644 +--- a/ssh.1 ++++ b/ssh.1 +@@ -1563,6 +1563,8 @@ The file format and configuration options are described in + .Xr ssh_config 5 . + Because of the potential for abuse, this file must have strict permissions: + read/write for the user, and not writable by others. ++It may be group-writable provided that the group in question contains only ++the user. + .Pp + .It Pa ~/.ssh/environment + Contains additional definitions for environment variables; see +diff --git a/ssh_config.5 b/ssh_config.5 +index 09905bff3..92600ed6e 100644 +--- a/ssh_config.5 ++++ b/ssh_config.5 +@@ -2258,6 +2258,8 @@ The format of this file is described above. + This file is used by the SSH client. + Because of the potential for abuse, this file must have strict permissions: + read/write for the user, and not writable by others. ++It may be group-writable provided that the group in question contains only ++the user. + .It Pa /etc/ssh/ssh_config + Systemwide configuration file. + This file provides defaults for those |