diff --git a/.gitignore b/.gitignore index f760b87..7004cb5 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,5 @@ pam_ssh_agent_auth-0.9.2.tar.bz2 /openssh-9.6p1.tar.gz.asc /openssh-9.8p1.tar.gz /openssh-9.8p1.tar.gz.asc +/openssh-9.9p1.tar.gz +/openssh-9.9p1.tar.gz.asc diff --git a/openssh-6.6p1-allow-ip-opts.patch b/openssh-6.6p1-allow-ip-opts.patch index d969b5c..995e04e 100644 --- a/openssh-6.6p1-allow-ip-opts.patch +++ b/openssh-6.6p1-allow-ip-opts.patch @@ -38,5 +38,5 @@ diff -up openssh/sshd.c.ip-opts openssh/sshd.c + } + } while (i < option_size); } - return; #endif /* IP_OPTIONS */ + } diff --git a/openssh-6.7p1-coverity.patch b/openssh-6.7p1-coverity.patch index d98da28..ffe0c69 100644 --- a/openssh-6.7p1-coverity.patch +++ b/openssh-6.7p1-coverity.patch @@ -73,22 +73,6 @@ diff -up openssh-8.5p1/loginrec.c.coverity openssh-8.5p1/loginrec.c strncpy(ut->ut_host, li->hostname, MIN_SIZEOF(ut->ut_host, li->hostname)); # endif -@@ -1690,6 +1692,7 @@ record_failed_login(struct ssh *ssh, con - - memset(&ut, 0, sizeof(ut)); - /* strncpy because we don't necessarily want nul termination */ -+ /* coverity[buffer_size_warning : FALSE] */ - strncpy(ut.ut_user, username, sizeof(ut.ut_user)); - strlcpy(ut.ut_line, "ssh:notty", sizeof(ut.ut_line)); - -@@ -1699,6 +1702,7 @@ record_failed_login(struct ssh *ssh, con - ut.ut_pid = getpid(); - - /* strncpy because we don't necessarily want nul termination */ -+ /* coverity[buffer_size_warning : FALSE] */ - strncpy(ut.ut_host, hostname, sizeof(ut.ut_host)); - - if (ssh_packet_connection_is_on_socket(ssh) && diff -up openssh-8.5p1/misc.c.coverity openssh-8.5p1/misc.c --- openssh-8.5p1/misc.c.coverity 2021-03-24 12:03:33.745967902 +0100 +++ openssh-8.5p1/misc.c 2021-03-24 13:31:47.037079617 +0100 diff --git a/openssh-7.6p1-audit.patch b/openssh-7.6p1-audit.patch index c884292..2c7ddc5 100644 --- a/openssh-7.6p1-audit.patch +++ b/openssh-7.6p1-audit.patch @@ -1086,7 +1086,7 @@ diff -up openssh-8.6p1/Makefile.in.audit openssh-8.6p1/Makefile.in --- openssh-8.6p1/Makefile.in.audit 2021-04-19 16:47:35.731061937 +0200 +++ openssh-8.6p1/Makefile.in 2021-04-19 16:47:35.756062129 +0200 @@ -112,7 +112,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ - kexsntrup761x25519.o sntrup761.o kexgen.o \ + kexsntrup761x25519.o kexmlkem768x25519.o sntrup761.o kexgen.o \ kexgssc.o \ sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ - sshbuf-io.o @@ -2056,7 +2056,7 @@ diff -up openssh-8.6p1/sshd-session.c.audit openssh-8.6p1/sshd-session.c #include "ssh-sandbox.h" #include "auth-options.h" #include "version.h" -@@ -260,8 +261,8 @@ struct sshbuf *loginmsg; +@@ -260,8 +261,44 @@ struct sshbuf *loginmsg; struct sshbuf *loginmsg; /* Prototypes for various functions defined later in this file. */ @@ -2064,6 +2064,42 @@ diff -up openssh-8.6p1/sshd-session.c.audit openssh-8.6p1/sshd-session.c -void demote_sensitive_data(void); +void destroy_sensitive_data(struct ssh *); +void demote_sensitive_data(struct ssh *); ++ ++static int ++sshkey_is_private(const struct sshkey *k) ++{ ++ switch (k->type) { ++#ifdef WITH_OPENSSL ++ case KEY_RSA_CERT: ++ case KEY_RSA: { ++ const BIGNUM *d; ++ const RSA *rsa = EVP_PKEY_get0_RSA(k->pkey); ++ RSA_get0_key(rsa, NULL, NULL, &d); ++ return d != NULL; ++ } ++ case KEY_DSA_CERT: ++ case KEY_DSA: { ++ const BIGNUM *priv_key; ++ DSA_get0_key(k->dsa, NULL, &priv_key); ++ return priv_key != NULL; ++ } ++#ifdef OPENSSL_HAS_ECC ++ case KEY_ECDSA_CERT: ++ case KEY_ECDSA: { ++ const EC_KEY * ecdsa = EVP_PKEY_get0_EC_KEY(k->pkey); ++ return EC_KEY_get0_private_key(ecdsa) != NULL; ++ } ++#endif /* OPENSSL_HAS_ECC */ ++#endif /* WITH_OPENSSL */ ++ case KEY_ED25519_CERT: ++ case KEY_ED25519: ++ return (k->ed25519_pk != NULL); ++ default: ++ /* fatal("key_is_private: bad key type %d", k->type); */ ++ return 0; ++ } ++} ++ static void do_ssh2_kex(struct ssh *); /* @@ -2222,7 +2258,7 @@ diff -up openssh-8.6p1/sshd-session.c.audit openssh-8.6p1/sshd-session.c if (the_active_state != NULL && the_authctxt != NULL) { @@ -2525,7 +2593,9 @@ cleanup_exit(int i) - _exit(EXIT_AUTH_ATTEMPTED); + } #ifdef SSH_AUDIT_EVENTS /* done after do_cleanup so it can cancel the PAM auth 'thread' */ - if (the_active_state != NULL && mm_is_monitor()) @@ -2231,57 +2267,4 @@ diff -up openssh-8.6p1/sshd-session.c.audit openssh-8.6p1/sshd-session.c + mm_is_monitor()) audit_event(the_active_state, SSH_CONNECTION_ABANDON); #endif - _exit(i); -diff -up openssh-8.6p1/sshkey.c.audit openssh-8.6p1/sshkey.c ---- openssh-8.6p1/sshkey.c.audit 2021-04-19 16:47:35.741062014 +0200 -+++ openssh-8.6p1/sshkey.c 2021-04-19 16:47:35.759062152 +0200 -@@ -371,6 +371,38 @@ sshkey_type_is_valid_ca(int type) - } - - int -+sshkey_is_private(const struct sshkey *k) -+{ -+ switch (k->type) { -+#ifdef WITH_OPENSSL -+ case KEY_RSA_CERT: -+ case KEY_RSA: { -+ const BIGNUM *d; -+ RSA_get0_key(k->rsa, NULL, NULL, &d); -+ return d != NULL; -+ } -+ case KEY_DSA_CERT: -+ case KEY_DSA: { -+ const BIGNUM *priv_key; -+ DSA_get0_key(k->dsa, NULL, &priv_key); -+ return priv_key != NULL; -+ } -+#ifdef OPENSSL_HAS_ECC -+ case KEY_ECDSA_CERT: -+ case KEY_ECDSA: -+ return EC_KEY_get0_private_key(k->ecdsa) != NULL; -+#endif /* OPENSSL_HAS_ECC */ -+#endif /* WITH_OPENSSL */ -+ case KEY_ED25519_CERT: -+ case KEY_ED25519: -+ return (k->ed25519_pk != NULL); -+ default: -+ /* fatal("key_is_private: bad key type %d", k->type); */ -+ return 0; -+ } -+} -+ -+int - sshkey_is_cert(const struct sshkey *k) - { - if (k == NULL) -diff -up openssh-8.6p1/sshkey.h.audit openssh-8.6p1/sshkey.h ---- openssh-8.6p1/sshkey.h.audit 2021-04-19 16:47:35.741062014 +0200 -+++ openssh-8.6p1/sshkey.h 2021-04-19 16:47:35.759062152 +0200 -@@ -189,6 +189,7 @@ int sshkey_shield_private(struct sshke - int sshkey_unshield_private(struct sshkey *); - - int sshkey_type_from_name(const char *); -+int sshkey_is_private(const struct sshkey *); - int sshkey_is_cert(const struct sshkey *); - int sshkey_is_sk(const struct sshkey *); - int sshkey_type_is_cert(int); + /* Override default fatal exit value when auth was attempted */ diff --git a/openssh-7.7p1-fips.patch b/openssh-7.7p1-fips.patch index 6a14cb3..a53f9f6 100644 --- a/openssh-7.7p1-fips.patch +++ b/openssh-7.7p1-fips.patch @@ -427,9 +427,9 @@ diff -up openssh-8.6p1/sshkey.c.fips openssh-8.6p1/sshkey.c --- openssh-8.6p1/sshkey.c.fips 2021-05-06 12:08:36.493926838 +0200 +++ openssh-8.6p1/sshkey.c 2021-05-06 12:08:36.502926908 +0200 @@ -36,6 +36,7 @@ + #include + #include #include - #include - #include +#include #endif @@ -544,13 +544,13 @@ diff -up openssh-8.6p1/ssh-keygen.c.fips openssh-8.6p1/ssh-keygen.c - name = _PATH_SSH_CLIENT_ID_ED25519; + name = FIPS_mode() ? _PATH_SSH_CLIENT_ID_RSA : _PATH_SSH_CLIENT_ID_ED25519; else { - switch (sshkey_type_from_name(key_type_name)) { + switch (sshkey_type_from_shortname(key_type_name)) { #ifdef WITH_DSA @@ -1098,9 +1104,17 @@ do_gen_all_hostkeys(struct passwd *pw) first = 1; printf("%s: generating new host keys: ", __progname); } -+ type = sshkey_type_from_name(key_types[i].key_type); ++ type = sshkey_type_from_shortname(key_types[i].key_type); + + /* Skip the keys that are not supported in FIPS mode */ + if (FIPS_mode() && (type == KEY_DSA || type == KEY_ED25519)) { @@ -561,7 +561,7 @@ diff -up openssh-8.6p1/ssh-keygen.c.fips openssh-8.6p1/ssh-keygen.c + printf("%s ", key_types[i].key_type_display); fflush(stdout); -- type = sshkey_type_from_name(key_types[i].key_type); +- type = sshkey_type_from_shortname(key_types[i].key_type); if ((fd = mkstemp(prv_tmp)) == -1) { error("Could not save your private key in %s: %s", prv_tmp, strerror(errno)); @@ -572,31 +572,31 @@ diff -up openssh-8.6p1/ssh-keygen.c.fips openssh-8.6p1/ssh-keygen.c - key_type_name = DEFAULT_KEY_TYPE_NAME; + key_type_name = FIPS_mode() ? FIPS_DEFAULT_KEY_TYPE_NAME : DEFAULT_KEY_TYPE_NAME; - type = sshkey_type_from_name(key_type_name); + type = sshkey_type_from_shortname(key_type_name); type_bits_valid(type, key_type_name, &bits); diff -up openssh-9.3p1/ssh-rsa.c.evpgenrsa openssh-9.3p1/ssh-rsa.c --- openssh-9.3p1/ssh-rsa.c.evpgenrsa 2022-06-30 15:14:58.200518353 +0200 +++ openssh-9.3p1/ssh-rsa.c 2022-06-30 15:24:31.499641196 +0200 @@ -33,6 +33,7 @@ + + #include #include - #include - #include +#include #include #include @@ -1705,6 +1707,8 @@ ssh_rsa_generate(u_int bits, RSA goto out; - - if (EVP_PKEY_keygen(ctx, &res) <= 0) { + } + if (EVP_PKEY_keygen(ctx, &res) <= 0 || res == NULL) { + if (FIPS_mode()) + logit_f("the key length might be unsupported by FIPS mode approved key generation method"); ret = SSH_ERR_LIBCRYPTO_ERROR; goto out; } -diff -up openssh-8.7p1/kexgen.c.fips3 openssh-8.7p1/kexgen.c ---- openssh-8.7p1/kexgen.c.fips3 2022-07-11 16:11:21.973519913 +0200 -+++ openssh-8.7p1/kexgen.c 2022-07-11 16:25:31.172187365 +0200 +diff -up openssh-9.9p1/kexgen.c.xxx openssh-9.9p1/kexgen.c +--- openssh-9.9p1/kexgen.c.xxx 2024-10-09 10:35:56.285946080 +0200 ++++ openssh-9.9p1/kexgen.c 2024-10-09 10:41:52.792597194 +0200 @@ -31,6 +31,7 @@ #include #include @@ -605,7 +605,7 @@ diff -up openssh-8.7p1/kexgen.c.fips3 openssh-8.7p1/kexgen.c #include "sshkey.h" #include "kex.h" -@@ -115,10 +116,20 @@ kex_gen_client(struct ssh *ssh) +@@ -115,13 +116,28 @@ kex_gen_client(struct ssh *ssh) break; #endif case KEX_C25519_SHA256: @@ -626,9 +626,18 @@ diff -up openssh-8.7p1/kexgen.c.fips3 openssh-8.7p1/kexgen.c + r = kex_kem_sntrup761x25519_keypair(kex); + } break; + case KEX_KEM_MLKEM768X25519_SHA256: +- r = kex_kem_mlkem768x25519_keypair(kex); ++ if (FIPS_mode()) { ++ logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode"); ++ r = SSH_ERR_INVALID_ARGUMENT; ++ } else { ++ r = kex_kem_mlkem768x25519_keypair(kex); ++ } + break; default: r = SSH_ERR_INVALID_ARGUMENT; -@@ -186,11 +197,21 @@ input_kex_gen_reply(int type, u_int32_t +@@ -189,15 +205,30 @@ input_kex_gen_reply(int type, u_int32_t break; #endif case KEX_C25519_SHA256: @@ -651,9 +660,20 @@ diff -up openssh-8.7p1/kexgen.c.fips3 openssh-8.7p1/kexgen.c + &shared_secret); + } break; + case KEX_KEM_MLKEM768X25519_SHA256: +- r = kex_kem_mlkem768x25519_dec(kex, server_blob, +- &shared_secret); ++ if (FIPS_mode()) { ++ logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode"); ++ r = SSH_ERR_INVALID_ARGUMENT; ++ } else { ++ r = kex_kem_mlkem768x25519_dec(kex, server_blob, ++ &shared_secret); ++ } + break; default: r = SSH_ERR_INVALID_ARGUMENT; -@@ -285,12 +306,22 @@ input_kex_gen_init(int type, u_int32_t s +@@ -312,16 +343,31 @@ input_kex_gen_init(int type, u_int32_t s break; #endif case KEX_C25519_SHA256: @@ -678,6 +698,17 @@ diff -up openssh-8.7p1/kexgen.c.fips3 openssh-8.7p1/kexgen.c + &server_pubkey, &shared_secret); + } break; + case KEX_KEM_MLKEM768X25519_SHA256: +- r = kex_kem_mlkem768x25519_enc(kex, client_pubkey, +- &server_pubkey, &shared_secret); ++ if (FIPS_mode()) { ++ logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode"); ++ r = SSH_ERR_INVALID_ARGUMENT; ++ } else { ++ r = kex_kem_mlkem768x25519_enc(kex, client_pubkey, ++ &server_pubkey, &shared_secret); ++ } + break; default: r = SSH_ERR_INVALID_ARGUMENT; diff -up openssh-8.7p1/ssh-ed25519.c.fips3 openssh-8.7p1/ssh-ed25519.c diff --git a/openssh-8.0p1-crypto-policies.patch b/openssh-8.0p1-crypto-policies.patch index fd1e59d..a666c5c 100644 --- a/openssh-8.0p1-crypto-policies.patch +++ b/openssh-8.0p1-crypto-policies.patch @@ -166,8 +166,8 @@ diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x +.Pp Specifies the permitted KEX (Key Exchange) algorithms that will be used and their preference order. - The selected algorithm will the the first algorithm in this list that -@@ -1338,28 +1343,17 @@ Multiple algorithms must be comma-separa + The selected algorithm will be the first algorithm in this list that +@@ -1338,29 +1343,17 @@ Multiple algorithms must be comma-separa .Pp If the specified list begins with a .Sq + @@ -187,7 +187,8 @@ diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x -.Pp -The default is: -.Bd -literal -offset indent --sntrup761x25519-sha512@openssh.com, +-sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com, +-mlkem768x25519-sha256, -curve25519-sha256,curve25519-sha256@libssh.org, -ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521, -diffie-hellman-group-exchange-sha256, @@ -517,13 +518,14 @@ diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x .Pp The supported algorithms are: .Pp -@@ -1075,16 +1080,6 @@ ecdh-sha2-nistp521 +@@ -1075,17 +1080,6 @@ ecdh-sha2-nistp521 sntrup761x25519-sha512@openssh.com .El .Pp -The default is: -.Bd -literal -offset indent --sntrup761x25519-sha512@openssh.com, +-sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com, +-mlkem768x25519-sha256, -curve25519-sha256,curve25519-sha256@libssh.org, -ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521, -diffie-hellman-group-exchange-sha256, diff --git a/openssh-8.0p1-gssapi-keyex.patch b/openssh-8.0p1-gssapi-keyex.patch deleted file mode 100644 index 82b2bfe..0000000 --- a/openssh-8.0p1-gssapi-keyex.patch +++ /dev/null @@ -1,3985 +0,0 @@ -diff --color -ruNp a/auth2.c b/auth2.c ---- a/auth2.c 2024-08-28 12:35:01.189659493 +0200 -+++ b/auth2.c 2024-08-28 12:35:41.246432045 +0200 -@@ -71,6 +71,7 @@ extern Authmethod method_passwd; - extern Authmethod method_kbdint; - extern Authmethod method_hostbased; - #ifdef GSSAPI -+extern Authmethod method_gsskeyex; - extern Authmethod method_gssapi; - #endif - -@@ -78,6 +79,7 @@ Authmethod *authmethods[] = { - &method_none, - &method_pubkey, - #ifdef GSSAPI -+ &method_gsskeyex, - &method_gssapi, - #endif - &method_passwd, -diff --color -ruNp a/auth2-gss.c b/auth2-gss.c ---- a/auth2-gss.c 2024-08-28 12:35:01.189659493 +0200 -+++ b/auth2-gss.c 2024-08-28 12:35:41.265432411 +0200 -@@ -51,6 +51,7 @@ - #define SSH_GSSAPI_MAX_MECHS 2048 - - extern ServerOptions options; -+extern struct authmethod_cfg methodcfg_gsskeyex; - extern struct authmethod_cfg methodcfg_gssapi; - - static int input_gssapi_token(int type, u_int32_t plen, struct ssh *ssh); -@@ -59,6 +60,48 @@ static int input_gssapi_exchange_complet - static int input_gssapi_errtok(int, u_int32_t, struct ssh *); - - /* -+ * The 'gssapi_keyex' userauth mechanism. -+ */ -+static int -+userauth_gsskeyex(struct ssh *ssh, const char *method) -+{ -+ Authctxt *authctxt = ssh->authctxt; -+ int r, authenticated = 0; -+ struct sshbuf *b = NULL; -+ gss_buffer_desc mic, gssbuf; -+ u_char *p; -+ size_t len; -+ -+ if ((r = sshpkt_get_string(ssh, &p, &len)) != 0 || -+ (r = sshpkt_get_end(ssh)) != 0) -+ fatal_fr(r, "parsing"); -+ -+ if ((b = sshbuf_new()) == NULL) -+ fatal_f("sshbuf_new failed"); -+ -+ mic.value = p; -+ mic.length = len; -+ -+ ssh_gssapi_buildmic(b, authctxt->user, authctxt->service, -+ "gssapi-keyex", ssh->kex->session_id); -+ -+ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL) -+ fatal_f("sshbuf_mutable_ptr failed"); -+ gssbuf.length = sshbuf_len(b); -+ -+ /* gss_kex_context is NULL with privsep, so we can't check it here */ -+ if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gss_kex_context, -+ &gssbuf, &mic))) -+ authenticated = mm_ssh_gssapi_userok(authctxt->user, -+ authctxt->pw, 1); -+ -+ sshbuf_free(b); -+ free(mic.value); -+ -+ return (authenticated); -+} -+ -+/* - * We only support those mechanisms that we know about (ie ones that we know - * how to check local user kuserok and the like) - */ -@@ -267,7 +310,7 @@ input_gssapi_exchange_complete(int type, - if ((r = sshpkt_get_end(ssh)) != 0) - fatal_fr(r, "parse packet"); - -- authenticated = mm_ssh_gssapi_userok(authctxt->user); -+ authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 1); - - authctxt->postponed = 0; - ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); -@@ -315,7 +358,7 @@ input_gssapi_mic(int type, u_int32_t ple - gssbuf.length = sshbuf_len(b); - - if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic))) -- authenticated = mm_ssh_gssapi_userok(authctxt->user); -+ authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 0); - else - logit("GSSAPI MIC check failed"); - -@@ -333,6 +376,11 @@ input_gssapi_mic(int type, u_int32_t ple - return 0; - } - -+Authmethod method_gsskeyex = { -+ &methodcfg_gsskeyex, -+ userauth_gsskeyex, -+}; -+ - Authmethod method_gssapi = { - &methodcfg_gssapi, - userauth_gssapi, -diff --color -ruNp a/auth2-methods.c b/auth2-methods.c ---- a/auth2-methods.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/auth2-methods.c 2024-08-28 12:35:41.265432411 +0200 -@@ -50,6 +50,11 @@ struct authmethod_cfg methodcfg_pubkey = - &options.pubkey_authentication - }; - #ifdef GSSAPI -+struct authmethod_cfg methodcfg_gsskeyex = { -+ "gssapi-keyex", -+ NULL, -+ &options.gss_authentication -+}; - struct authmethod_cfg methodcfg_gssapi = { - "gssapi-with-mic", - NULL, -@@ -76,6 +81,7 @@ static struct authmethod_cfg *authmethod - &methodcfg_none, - &methodcfg_pubkey, - #ifdef GSSAPI -+ &methodcfg_gsskeyex, - &methodcfg_gssapi, - #endif - &methodcfg_passwd, -diff --color -ruNp a/auth.c b/auth.c ---- a/auth.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/auth.c 2024-08-28 12:35:41.245432026 +0200 -@@ -356,7 +356,8 @@ auth_root_allowed(struct ssh *ssh, const - case PERMIT_NO_PASSWD: - if (strcmp(method, "publickey") == 0 || - strcmp(method, "hostbased") == 0 || -- strcmp(method, "gssapi-with-mic") == 0) -+ strcmp(method, "gssapi-with-mic") == 0 || -+ strcmp(method, "gssapi-keyex") == 0) - return 1; - break; - case PERMIT_FORCED_ONLY: -diff --color -ruNp a/canohost.c b/canohost.c ---- a/canohost.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/canohost.c 2024-08-28 12:35:41.246432045 +0200 -@@ -35,6 +35,99 @@ - #include "canohost.h" - #include "misc.h" - -+/* -+ * Returns the remote DNS hostname as a string. The returned string must not -+ * be freed. NB. this will usually trigger a DNS query the first time it is -+ * called. -+ * This function does additional checks on the hostname to mitigate some -+ * attacks on legacy rhosts-style authentication. -+ * XXX is RhostsRSAAuthentication vulnerable to these? -+ * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?) -+ */ -+ -+char * -+remote_hostname(struct ssh *ssh) -+{ -+ struct sockaddr_storage from; -+ socklen_t fromlen; -+ struct addrinfo hints, *ai, *aitop; -+ char name[NI_MAXHOST], ntop2[NI_MAXHOST]; -+ const char *ntop = ssh_remote_ipaddr(ssh); -+ -+ /* Get IP address of client. */ -+ fromlen = sizeof(from); -+ memset(&from, 0, sizeof(from)); -+ if (getpeername(ssh_packet_get_connection_in(ssh), -+ (struct sockaddr *)&from, &fromlen) == -1) { -+ debug("getpeername failed: %.100s", strerror(errno)); -+ return xstrdup(ntop); -+ } -+ -+ ipv64_normalise_mapped(&from, &fromlen); -+ if (from.ss_family == AF_INET6) -+ fromlen = sizeof(struct sockaddr_in6); -+ -+ debug3("Trying to reverse map address %.100s.", ntop); -+ /* Map the IP address to a host name. */ -+ if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name), -+ NULL, 0, NI_NAMEREQD) != 0) { -+ /* Host name not found. Use ip address. */ -+ return xstrdup(ntop); -+ } -+ -+ /* -+ * if reverse lookup result looks like a numeric hostname, -+ * someone is trying to trick us by PTR record like following: -+ * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5 -+ */ -+ memset(&hints, 0, sizeof(hints)); -+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/ -+ hints.ai_flags = AI_NUMERICHOST; -+ if (getaddrinfo(name, NULL, &hints, &ai) == 0) { -+ logit("Nasty PTR record \"%s\" is set up for %s, ignoring", -+ name, ntop); -+ freeaddrinfo(ai); -+ return xstrdup(ntop); -+ } -+ -+ /* Names are stored in lowercase. */ -+ lowercase(name); -+ -+ /* -+ * Map it back to an IP address and check that the given -+ * address actually is an address of this host. This is -+ * necessary because anyone with access to a name server can -+ * define arbitrary names for an IP address. Mapping from -+ * name to IP address can be trusted better (but can still be -+ * fooled if the intruder has access to the name server of -+ * the domain). -+ */ -+ memset(&hints, 0, sizeof(hints)); -+ hints.ai_family = from.ss_family; -+ hints.ai_socktype = SOCK_STREAM; -+ if (getaddrinfo(name, NULL, &hints, &aitop) != 0) { -+ logit("reverse mapping checking getaddrinfo for %.700s " -+ "[%s] failed.", name, ntop); -+ return xstrdup(ntop); -+ } -+ /* Look for the address from the list of addresses. */ -+ for (ai = aitop; ai; ai = ai->ai_next) { -+ if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2, -+ sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 && -+ (strcmp(ntop, ntop2) == 0)) -+ break; -+ } -+ freeaddrinfo(aitop); -+ /* If we reached the end of the list, the address was not there. */ -+ if (ai == NULL) { -+ /* Address not found for the host name. */ -+ logit("Address %.100s maps to %.600s, but this does not " -+ "map back to the address.", ntop, name); -+ return xstrdup(ntop); -+ } -+ return xstrdup(name); -+} -+ - void - ipv64_normalise_mapped(struct sockaddr_storage *addr, socklen_t *len) - { -diff --color -ruNp a/canohost.h b/canohost.h ---- a/canohost.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/canohost.h 2024-08-28 12:35:41.246432045 +0200 -@@ -15,6 +15,9 @@ - #ifndef _CANOHOST_H - #define _CANOHOST_H - -+struct ssh; -+ -+char *remote_hostname(struct ssh *); - char *get_peer_ipaddr(int); - int get_peer_port(int); - char *get_local_ipaddr(int); -diff --color -ruNp a/clientloop.c b/clientloop.c ---- a/clientloop.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/clientloop.c 2024-08-28 12:35:41.246432045 +0200 -@@ -115,6 +115,10 @@ - #include "ssherr.h" - #include "hostfile.h" - -+#ifdef GSSAPI -+#include "ssh-gss.h" -+#endif -+ - /* Permitted RSA signature algorithms for UpdateHostkeys proofs */ - #define HOSTKEY_PROOF_RSA_ALGS "rsa-sha2-512,rsa-sha2-256" - -@@ -1590,6 +1594,14 @@ client_loop(struct ssh *ssh, int have_pt - /* Do channel operations. */ - channel_after_poll(ssh, pfd, npfd_active); - -+#ifdef GSSAPI -+ if (options.gss_renewal_rekey && -+ ssh_gssapi_credentials_updated(NULL)) { -+ debug("credentials updated - forcing rekey"); -+ need_rekeying = 1; -+ } -+#endif -+ - /* Buffer input from the connection. */ - if (conn_in_ready) - client_process_net_input(ssh); -diff --color -ruNp a/configure.ac b/configure.ac ---- a/configure.ac 2024-08-28 12:35:01.202659743 +0200 -+++ b/configure.ac 2024-08-28 12:35:41.247432064 +0200 -@@ -774,6 +774,30 @@ int main(void) { if (NSVersionOfRunTimeL - [Use tunnel device compatibility to OpenBSD]) - AC_DEFINE([SSH_TUN_PREPEND_AF], [1], - [Prepend the address family to IP tunnel traffic]) -+ AC_MSG_CHECKING([if we have the Security Authorization Session API]) -+ AC_TRY_COMPILE([#include ], -+ [SessionCreate(0, 0);], -+ [ac_cv_use_security_session_api="yes" -+ AC_DEFINE([USE_SECURITY_SESSION_API], [1], -+ [platform has the Security Authorization Session API]) -+ LIBS="$LIBS -framework Security" -+ AC_MSG_RESULT([yes])], -+ [ac_cv_use_security_session_api="no" -+ AC_MSG_RESULT([no])]) -+ AC_MSG_CHECKING([if we have an in-memory credentials cache]) -+ AC_TRY_COMPILE( -+ [#include ], -+ [cc_context_t c; -+ (void) cc_initialize (&c, 0, NULL, NULL);], -+ [AC_DEFINE([USE_CCAPI], [1], -+ [platform uses an in-memory credentials cache]) -+ LIBS="$LIBS -framework Security" -+ AC_MSG_RESULT([yes]) -+ if test "x$ac_cv_use_security_session_api" = "xno"; then -+ AC_MSG_ERROR([*** Need a security framework to use the credentials cache API ***]) -+ fi], -+ [AC_MSG_RESULT([no])] -+ ) - m4_pattern_allow([AU_IPv]) - AC_CHECK_DECL([AU_IPv4], [], - AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records]) -diff --color -ruNp a/gss-genr.c b/gss-genr.c ---- a/gss-genr.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/gss-genr.c 2024-08-28 12:35:41.248432084 +0200 -@@ -42,9 +42,33 @@ - #include "sshbuf.h" - #include "log.h" - #include "ssh2.h" -+#include "cipher.h" -+#include "sshkey.h" -+#include "kex.h" -+#include "digest.h" -+#include "packet.h" - - #include "ssh-gss.h" - -+typedef struct { -+ char *encoded; -+ gss_OID oid; -+} ssh_gss_kex_mapping; -+ -+/* -+ * XXX - It would be nice to find a more elegant way of handling the -+ * XXX passing of the key exchange context to the userauth routines -+ */ -+ -+Gssctxt *gss_kex_context = NULL; -+ -+static ssh_gss_kex_mapping *gss_enc2oid = NULL; -+ -+int -+ssh_gssapi_oid_table_ok(void) { -+ return (gss_enc2oid != NULL); -+} -+ - /* sshbuf_get for gss_buffer_desc */ - int - ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g) -@@ -60,6 +84,159 @@ ssh_gssapi_get_buffer_desc(struct sshbuf - return 0; - } - -+/* sshpkt_get of gss_buffer_desc */ -+int -+ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *ssh, gss_buffer_desc *g) -+{ -+ int r; -+ u_char *p; -+ size_t len; -+ -+ if ((r = sshpkt_get_string(ssh, &p, &len)) != 0) -+ return r; -+ g->value = p; -+ g->length = len; -+ return 0; -+} -+ -+/* -+ * Return a list of the gss-group1-sha1 mechanisms supported by this program -+ * -+ * We test mechanisms to ensure that we can use them, to avoid starting -+ * a key exchange with a bad mechanism -+ */ -+ -+char * -+ssh_gssapi_client_mechanisms(const char *host, const char *client, -+ const char *kex) { -+ gss_OID_set gss_supported = NULL; -+ OM_uint32 min_status; -+ -+ if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported))) -+ return NULL; -+ -+ return ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism, -+ host, client, kex); -+} -+ -+char * -+ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check, -+ const char *host, const char *client, const char *kex) { -+ struct sshbuf *buf = NULL; -+ size_t i; -+ int r = SSH_ERR_ALLOC_FAIL; -+ int oidpos, enclen; -+ char *mechs, *encoded; -+ u_char digest[SSH_DIGEST_MAX_LENGTH]; -+ char deroid[2]; -+ struct ssh_digest_ctx *md = NULL; -+ char *s, *cp, *p; -+ -+ if (gss_enc2oid != NULL) { -+ for (i = 0; gss_enc2oid[i].encoded != NULL; i++) -+ free(gss_enc2oid[i].encoded); -+ free(gss_enc2oid); -+ } -+ -+ gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) * -+ (gss_supported->count + 1)); -+ -+ if ((buf = sshbuf_new()) == NULL) -+ fatal_f("sshbuf_new failed"); -+ -+ oidpos = 0; -+ s = cp = xstrdup(kex); -+ for (i = 0; i < gss_supported->count; i++) { -+ if (gss_supported->elements[i].length < 128 && -+ (*check)(NULL, &(gss_supported->elements[i]), host, client)) { -+ -+ deroid[0] = SSH_GSS_OIDTYPE; -+ deroid[1] = gss_supported->elements[i].length; -+ -+ if ((md = ssh_digest_start(SSH_DIGEST_MD5)) == NULL || -+ (r = ssh_digest_update(md, deroid, 2)) != 0 || -+ (r = ssh_digest_update(md, -+ gss_supported->elements[i].elements, -+ gss_supported->elements[i].length)) != 0 || -+ (r = ssh_digest_final(md, digest, sizeof(digest))) != 0) -+ fatal_fr(r, "digest failed"); -+ ssh_digest_free(md); -+ md = NULL; -+ -+ encoded = xmalloc(ssh_digest_bytes(SSH_DIGEST_MD5) -+ * 2); -+ enclen = __b64_ntop(digest, -+ ssh_digest_bytes(SSH_DIGEST_MD5), encoded, -+ ssh_digest_bytes(SSH_DIGEST_MD5) * 2); -+ -+ cp = strncpy(s, kex, strlen(kex)); -+ for ((p = strsep(&cp, ",")); p && *p != '\0'; -+ (p = strsep(&cp, ","))) { -+ if (sshbuf_len(buf) != 0 && -+ (r = sshbuf_put_u8(buf, ',')) != 0) -+ fatal_fr(r, "sshbuf_put_u8 error"); -+ if ((r = sshbuf_put(buf, p, strlen(p))) != 0 || -+ (r = sshbuf_put(buf, encoded, enclen)) != 0) -+ fatal_fr(r, "sshbuf_put error"); -+ } -+ -+ gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]); -+ gss_enc2oid[oidpos].encoded = encoded; -+ oidpos++; -+ } -+ } -+ free(s); -+ gss_enc2oid[oidpos].oid = NULL; -+ gss_enc2oid[oidpos].encoded = NULL; -+ -+ if ((mechs = sshbuf_dup_string(buf)) == NULL) -+ fatal_f("sshbuf_dup_string failed"); -+ -+ sshbuf_free(buf); -+ -+ if (strlen(mechs) == 0) { -+ free(mechs); -+ mechs = NULL; -+ } -+ -+ return (mechs); -+} -+ -+gss_OID -+ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) { -+ int i = 0; -+ -+#define SKIP_KEX_NAME(type) \ -+ case type: \ -+ if (strlen(name) < sizeof(type##_ID)) \ -+ return GSS_C_NO_OID; \ -+ name += sizeof(type##_ID) - 1; \ -+ break; -+ -+ switch (kex_type) { -+ SKIP_KEX_NAME(KEX_GSS_GRP1_SHA1) -+ SKIP_KEX_NAME(KEX_GSS_GRP14_SHA1) -+ SKIP_KEX_NAME(KEX_GSS_GRP14_SHA256) -+ SKIP_KEX_NAME(KEX_GSS_GRP16_SHA512) -+ SKIP_KEX_NAME(KEX_GSS_GEX_SHA1) -+ SKIP_KEX_NAME(KEX_GSS_NISTP256_SHA256) -+ SKIP_KEX_NAME(KEX_GSS_C25519_SHA256) -+ default: -+ return GSS_C_NO_OID; -+ } -+ -+#undef SKIP_KEX_NAME -+ -+ while (gss_enc2oid[i].encoded != NULL && -+ strcmp(name, gss_enc2oid[i].encoded) != 0) -+ i++; -+ -+ if (gss_enc2oid[i].oid != NULL && ctx != NULL) -+ ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid); -+ -+ return gss_enc2oid[i].oid; -+} -+ - /* Check that the OID in a data stream matches that in the context */ - int - ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) -@@ -216,7 +393,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int de - } - - ctx->major = gss_init_sec_context(&ctx->minor, -- GSS_C_NO_CREDENTIAL, &ctx->context, ctx->name, ctx->oid, -+ ctx->client_creds, &ctx->context, ctx->name, ctx->oid, - GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, - 0, NULL, recv_tok, NULL, send_tok, flags, NULL); - -@@ -246,8 +423,42 @@ ssh_gssapi_import_name(Gssctxt *ctx, con - } - - OM_uint32 -+ssh_gssapi_client_identity(Gssctxt *ctx, const char *name) -+{ -+ gss_buffer_desc gssbuf; -+ gss_name_t gssname; -+ OM_uint32 status; -+ gss_OID_set oidset; -+ -+ gssbuf.value = (void *) name; -+ gssbuf.length = strlen(gssbuf.value); -+ -+ gss_create_empty_oid_set(&status, &oidset); -+ gss_add_oid_set_member(&status, ctx->oid, &oidset); -+ -+ ctx->major = gss_import_name(&ctx->minor, &gssbuf, -+ GSS_C_NT_USER_NAME, &gssname); -+ -+ if (!ctx->major) -+ ctx->major = gss_acquire_cred(&ctx->minor, -+ gssname, 0, oidset, GSS_C_INITIATE, -+ &ctx->client_creds, NULL, NULL); -+ -+ gss_release_name(&status, &gssname); -+ gss_release_oid_set(&status, &oidset); -+ -+ if (ctx->major) -+ ssh_gssapi_error(ctx); -+ -+ return(ctx->major); -+} -+ -+OM_uint32 - ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) - { -+ if (ctx == NULL) -+ return -1; -+ - if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, - GSS_C_QOP_DEFAULT, buffer, hash))) - ssh_gssapi_error(ctx); -@@ -255,6 +466,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer - return (ctx->major); - } - -+/* Priviledged when used by server */ -+OM_uint32 -+ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) -+{ -+ if (ctx == NULL) -+ return -1; -+ -+ ctx->major = gss_verify_mic(&ctx->minor, ctx->context, -+ gssbuf, gssmic, NULL); -+ -+ return (ctx->major); -+} -+ - void - ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service, - const char *context, const struct sshbuf *session_id) -@@ -271,11 +495,16 @@ ssh_gssapi_buildmic(struct sshbuf *b, co - } - - int --ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) -+ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host, -+ const char *client) - { - gss_buffer_desc token = GSS_C_EMPTY_BUFFER; - OM_uint32 major, minor; - gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; -+ Gssctxt *intctx = NULL; -+ -+ if (ctx == NULL) -+ ctx = &intctx; - - /* RFC 4462 says we MUST NOT do SPNEGO */ - if (oid->length == spnego_oid.length && -@@ -285,6 +514,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx - ssh_gssapi_build_ctx(ctx); - ssh_gssapi_set_oid(*ctx, oid); - major = ssh_gssapi_import_name(*ctx, host); -+ -+ if (!GSS_ERROR(major) && client) -+ major = ssh_gssapi_client_identity(*ctx, client); -+ - if (!GSS_ERROR(major)) { - major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, - NULL); -@@ -294,10 +527,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx - GSS_C_NO_BUFFER); - } - -- if (GSS_ERROR(major)) -+ if (GSS_ERROR(major) || intctx != NULL) - ssh_gssapi_delete_ctx(ctx); - - return (!GSS_ERROR(major)); - } - -+int -+ssh_gssapi_credentials_updated(Gssctxt *ctxt) { -+ static gss_name_t saved_name = GSS_C_NO_NAME; -+ static OM_uint32 saved_lifetime = 0; -+ static gss_OID saved_mech = GSS_C_NO_OID; -+ static gss_name_t name; -+ static OM_uint32 last_call = 0; -+ OM_uint32 lifetime, now, major, minor; -+ int equal; -+ -+ now = time(NULL); -+ -+ if (ctxt) { -+ debug("Rekey has happened - updating saved versions"); -+ -+ if (saved_name != GSS_C_NO_NAME) -+ gss_release_name(&minor, &saved_name); -+ -+ major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, -+ &saved_name, &saved_lifetime, NULL, NULL); -+ -+ if (!GSS_ERROR(major)) { -+ saved_mech = ctxt->oid; -+ saved_lifetime+= now; -+ } else { -+ /* Handle the error */ -+ } -+ return 0; -+ } -+ -+ if (now - last_call < 10) -+ return 0; -+ -+ last_call = now; -+ -+ if (saved_mech == GSS_C_NO_OID) -+ return 0; -+ -+ major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, -+ &name, &lifetime, NULL, NULL); -+ if (major == GSS_S_CREDENTIALS_EXPIRED) -+ return 0; -+ else if (GSS_ERROR(major)) -+ return 0; -+ -+ major = gss_compare_name(&minor, saved_name, name, &equal); -+ gss_release_name(&minor, &name); -+ if (GSS_ERROR(major)) -+ return 0; -+ -+ if (equal && (saved_lifetime < lifetime + now - 10)) -+ return 1; -+ -+ return 0; -+} -+ - #endif /* GSSAPI */ -diff --color -ruNp a/gss-serv.c b/gss-serv.c ---- a/gss-serv.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/gss-serv.c 2024-08-28 12:35:41.248432084 +0200 -@@ -1,7 +1,7 @@ - /* $OpenBSD: gss-serv.c,v 1.32 2020/03/13 03:17:07 djm Exp $ */ - - /* -- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. -+ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions -@@ -44,17 +44,19 @@ - #include "session.h" - #include "misc.h" - #include "servconf.h" -+#include "uidswap.h" - - #include "ssh-gss.h" -+#include "monitor_wrap.h" - - extern ServerOptions options; - - static ssh_gssapi_client gssapi_client = -- { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, -- GSS_C_NO_CREDENTIAL, NULL, {NULL, NULL, NULL, NULL}}; -+ { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL, -+ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0}; - - ssh_gssapi_mech gssapi_null_mech = -- { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL}; -+ { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL}; - - #ifdef KRB5 - extern ssh_gssapi_mech gssapi_kerberos_mech; -@@ -141,6 +143,29 @@ ssh_gssapi_server_ctx(Gssctxt **ctx, gss - } - - /* Unprivileged */ -+char * -+ssh_gssapi_server_mechanisms(void) { -+ if (supported_oids == NULL) -+ ssh_gssapi_prepare_supported_oids(); -+ return (ssh_gssapi_kex_mechs(supported_oids, -+ &ssh_gssapi_server_check_mech, NULL, NULL, -+ options.gss_kex_algorithms)); -+} -+ -+/* Unprivileged */ -+int -+ssh_gssapi_server_check_mech(Gssctxt **dum, gss_OID oid, const char *data, -+ const char *dummy) { -+ Gssctxt *ctx = NULL; -+ int res; -+ -+ res = !GSS_ERROR(mm_ssh_gssapi_server_ctx(&ctx, oid)); -+ ssh_gssapi_delete_ctx(&ctx); -+ -+ return (res); -+} -+ -+/* Unprivileged */ - void - ssh_gssapi_supported_oids(gss_OID_set *oidset) - { -@@ -150,7 +175,9 @@ ssh_gssapi_supported_oids(gss_OID_set *o - gss_OID_set supported; - - gss_create_empty_oid_set(&min_status, oidset); -- gss_indicate_mechs(&min_status, &supported); -+ -+ if (GSS_ERROR(gss_indicate_mechs(&min_status, &supported))) -+ return; - - while (supported_mechs[i]->name != NULL) { - if (GSS_ERROR(gss_test_oid_set_member(&min_status, -@@ -276,8 +303,48 @@ OM_uint32 - ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) - { - int i = 0; -+ int equal = 0; -+ gss_name_t new_name = GSS_C_NO_NAME; -+ gss_buffer_desc ename = GSS_C_EMPTY_BUFFER; -+ -+ if (options.gss_store_rekey && client->used && ctx->client_creds) { -+ if (client->mech->oid.length != ctx->oid->length || -+ (memcmp(client->mech->oid.elements, -+ ctx->oid->elements, ctx->oid->length) !=0)) { -+ debug("Rekeyed credentials have different mechanism"); -+ return GSS_S_COMPLETE; -+ } - -- gss_buffer_desc ename; -+ if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor, -+ ctx->client_creds, ctx->oid, &new_name, -+ NULL, NULL, NULL))) { -+ ssh_gssapi_error(ctx); -+ return (ctx->major); -+ } -+ -+ ctx->major = gss_compare_name(&ctx->minor, client->name, -+ new_name, &equal); -+ -+ if (GSS_ERROR(ctx->major)) { -+ ssh_gssapi_error(ctx); -+ return (ctx->major); -+ } -+ -+ if (!equal) { -+ debug("Rekeyed credentials have different name"); -+ return GSS_S_COMPLETE; -+ } -+ -+ debug("Marking rekeyed credentials for export"); -+ -+ gss_release_name(&ctx->minor, &client->name); -+ gss_release_cred(&ctx->minor, &client->creds); -+ client->name = new_name; -+ client->creds = ctx->client_creds; -+ ctx->client_creds = GSS_C_NO_CREDENTIAL; -+ client->updated = 1; -+ return GSS_S_COMPLETE; -+ } - - client->mech = NULL; - -@@ -292,6 +359,13 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g - if (client->mech == NULL) - return GSS_S_FAILURE; - -+ if (ctx->client_creds && -+ (ctx->major = gss_inquire_cred_by_mech(&ctx->minor, -+ ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) { -+ ssh_gssapi_error(ctx); -+ return (ctx->major); -+ } -+ - if ((ctx->major = gss_display_name(&ctx->minor, ctx->client, - &client->displayname, NULL))) { - ssh_gssapi_error(ctx); -@@ -309,6 +383,8 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g - return (ctx->major); - } - -+ gss_release_buffer(&ctx->minor, &ename); -+ - /* We can't copy this structure, so we just move the pointer to it */ - client->creds = ctx->client_creds; - ctx->client_creds = GSS_C_NO_CREDENTIAL; -@@ -319,11 +395,20 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g - void - ssh_gssapi_cleanup_creds(void) - { -- if (gssapi_client.store.filename != NULL) { -- /* Unlink probably isn't sufficient */ -- debug("removing gssapi cred file\"%s\"", -- gssapi_client.store.filename); -- unlink(gssapi_client.store.filename); -+ krb5_ccache ccache = NULL; -+ krb5_error_code problem; -+ -+ if (gssapi_client.store.data != NULL) { -+ if ((problem = krb5_cc_resolve(gssapi_client.store.data, gssapi_client.store.envval, &ccache))) { -+ debug_f("krb5_cc_resolve(): %.100s", -+ krb5_get_err_text(gssapi_client.store.data, problem)); -+ } else if ((problem = krb5_cc_destroy(gssapi_client.store.data, ccache))) { -+ debug_f("krb5_cc_destroy(): %.100s", -+ krb5_get_err_text(gssapi_client.store.data, problem)); -+ } else { -+ krb5_free_context(gssapi_client.store.data); -+ gssapi_client.store.data = NULL; -+ } - } - } - -@@ -356,19 +441,23 @@ ssh_gssapi_do_child(char ***envp, u_int - - /* Privileged */ - int --ssh_gssapi_userok(char *user) -+ssh_gssapi_userok(char *user, struct passwd *pw, int kex) - { - OM_uint32 lmin; - -+ (void) kex; /* used in privilege separation */ -+ - if (gssapi_client.exportedname.length == 0 || - gssapi_client.exportedname.value == NULL) { - debug("No suitable client data"); - return 0; - } - if (gssapi_client.mech && gssapi_client.mech->userok) -- if ((*gssapi_client.mech->userok)(&gssapi_client, user)) -+ if ((*gssapi_client.mech->userok)(&gssapi_client, user)) { -+ gssapi_client.used = 1; -+ gssapi_client.store.owner = pw; - return 1; -- else { -+ } else { - /* Destroy delegated credentials if userok fails */ - gss_release_buffer(&lmin, &gssapi_client.displayname); - gss_release_buffer(&lmin, &gssapi_client.exportedname); -@@ -382,14 +471,85 @@ ssh_gssapi_userok(char *user) - return (0); - } - --/* Privileged */ --OM_uint32 --ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) -+/* These bits are only used for rekeying. The unpriviledged child is running -+ * as the user, the monitor is root. -+ * -+ * In the child, we want to : -+ * *) Ask the monitor to store our credentials into the store we specify -+ * *) If it succeeds, maybe do a PAM update -+ */ -+ -+/* Stuff for PAM */ -+ -+#ifdef USE_PAM -+static int ssh_gssapi_simple_conv(int n, const struct pam_message **msg, -+ struct pam_response **resp, void *data) - { -- ctx->major = gss_verify_mic(&ctx->minor, ctx->context, -- gssbuf, gssmic, NULL); -+ return (PAM_CONV_ERR); -+} -+#endif - -- return (ctx->major); -+void -+ssh_gssapi_rekey_creds(void) { -+ int ok; -+#ifdef USE_PAM -+ int ret; -+ pam_handle_t *pamh = NULL; -+ struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL}; -+ char *envstr; -+#endif -+ -+ if (gssapi_client.store.filename == NULL && -+ gssapi_client.store.envval == NULL && -+ gssapi_client.store.envvar == NULL) -+ return; -+ -+ ok = mm_ssh_gssapi_update_creds(&gssapi_client.store); -+ -+ if (!ok) -+ return; -+ -+ debug("Rekeyed credentials stored successfully"); -+ -+ /* Actually managing to play with the ssh pam stack from here will -+ * be next to impossible. In any case, we may want different options -+ * for rekeying. So, use our own :) -+ */ -+#ifdef USE_PAM -+ ret = pam_start("sshd-rekey", gssapi_client.store.owner->pw_name, -+ &pamconv, &pamh); -+ if (ret) -+ return; -+ -+ xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar, -+ gssapi_client.store.envval); -+ -+ ret = pam_putenv(pamh, envstr); -+ if (!ret) -+ pam_setcred(pamh, PAM_REINITIALIZE_CRED); -+ pam_end(pamh, PAM_SUCCESS); -+#endif -+} -+ -+int -+ssh_gssapi_update_creds(ssh_gssapi_ccache *store) { -+ int ok = 0; -+ -+ /* Check we've got credentials to store */ -+ if (!gssapi_client.updated) -+ return 0; -+ -+ gssapi_client.updated = 0; -+ -+ temporarily_use_uid(gssapi_client.store.owner); -+ if (gssapi_client.mech && gssapi_client.mech->updatecreds) -+ ok = (*gssapi_client.mech->updatecreds)(store, &gssapi_client); -+ else -+ debug("No update function for this mechanism"); -+ -+ restore_uid(); -+ -+ return ok; - } - - /* Privileged */ -diff --color -ruNp a/gss-serv-krb5.c b/gss-serv-krb5.c ---- a/gss-serv-krb5.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/gss-serv-krb5.c 2024-08-28 12:35:41.248432084 +0200 -@@ -1,7 +1,7 @@ - /* $OpenBSD: gss-serv-krb5.c,v 1.9 2018/07/09 21:37:55 markus Exp $ */ - - /* -- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. -+ * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions -@@ -120,7 +120,7 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl - krb5_error_code problem; - krb5_principal princ; - OM_uint32 maj_status, min_status; -- int len; -+ const char *new_ccname, *new_cctype; - const char *errmsg; - - if (client->creds == NULL) { -@@ -180,11 +180,26 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl - return; - } - -- client->store.filename = xstrdup(krb5_cc_get_name(krb_context, ccache)); -+ new_cctype = krb5_cc_get_type(krb_context, ccache); -+ new_ccname = krb5_cc_get_name(krb_context, ccache); -+ - client->store.envvar = "KRB5CCNAME"; -- len = strlen(client->store.filename) + 6; -- client->store.envval = xmalloc(len); -- snprintf(client->store.envval, len, "FILE:%s", client->store.filename); -+#ifdef USE_CCAPI -+ xasprintf(&client->store.envval, "API:%s", new_ccname); -+ client->store.filename = NULL; -+#else -+ if (new_ccname[0] == ':') -+ new_ccname++; -+ xasprintf(&client->store.envval, "%s:%s", new_cctype, new_ccname); -+ if (strcmp(new_cctype, "DIR") == 0) { -+ char *p; -+ p = strrchr(client->store.envval, '/'); -+ if (p) -+ *p = '\0'; -+ } -+ if ((strcmp(new_cctype, "FILE") == 0) || (strcmp(new_cctype, "DIR") == 0)) -+ client->store.filename = xstrdup(new_ccname); -+#endif - - #ifdef USE_PAM - if (options.use_pam) -@@ -193,9 +208,76 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl - - krb5_cc_close(krb_context, ccache); - -+ client->store.data = krb_context; -+ - return; - } - -+int -+ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store, -+ ssh_gssapi_client *client) -+{ -+ krb5_ccache ccache = NULL; -+ krb5_principal principal = NULL; -+ char *name = NULL; -+ krb5_error_code problem; -+ OM_uint32 maj_status, min_status; -+ -+ if ((problem = krb5_cc_resolve(krb_context, store->envval, &ccache))) { -+ logit("krb5_cc_resolve(): %.100s", -+ krb5_get_err_text(krb_context, problem)); -+ return 0; -+ } -+ -+ /* Find out who the principal in this cache is */ -+ if ((problem = krb5_cc_get_principal(krb_context, ccache, -+ &principal))) { -+ logit("krb5_cc_get_principal(): %.100s", -+ krb5_get_err_text(krb_context, problem)); -+ krb5_cc_close(krb_context, ccache); -+ return 0; -+ } -+ -+ if ((problem = krb5_unparse_name(krb_context, principal, &name))) { -+ logit("krb5_unparse_name(): %.100s", -+ krb5_get_err_text(krb_context, problem)); -+ krb5_free_principal(krb_context, principal); -+ krb5_cc_close(krb_context, ccache); -+ return 0; -+ } -+ -+ -+ if (strcmp(name,client->exportedname.value)!=0) { -+ debug("Name in local credentials cache differs. Not storing"); -+ krb5_free_principal(krb_context, principal); -+ krb5_cc_close(krb_context, ccache); -+ krb5_free_unparsed_name(krb_context, name); -+ return 0; -+ } -+ krb5_free_unparsed_name(krb_context, name); -+ -+ /* Name matches, so lets get on with it! */ -+ -+ if ((problem = krb5_cc_initialize(krb_context, ccache, principal))) { -+ logit("krb5_cc_initialize(): %.100s", -+ krb5_get_err_text(krb_context, problem)); -+ krb5_free_principal(krb_context, principal); -+ krb5_cc_close(krb_context, ccache); -+ return 0; -+ } -+ -+ krb5_free_principal(krb_context, principal); -+ -+ if ((maj_status = gss_krb5_copy_ccache(&min_status, client->creds, -+ ccache))) { -+ logit("gss_krb5_copy_ccache() failed. Sorry!"); -+ krb5_cc_close(krb_context, ccache); -+ return 0; -+ } -+ -+ return 1; -+} -+ - ssh_gssapi_mech gssapi_kerberos_mech = { - "toWM5Slw5Ew8Mqkay+al2g==", - "Kerberos", -@@ -203,7 +285,8 @@ ssh_gssapi_mech gssapi_kerberos_mech = { - NULL, - &ssh_gssapi_krb5_userok, - NULL, -- &ssh_gssapi_krb5_storecreds -+ &ssh_gssapi_krb5_storecreds, -+ &ssh_gssapi_krb5_updatecreds - }; - - #endif /* KRB5 */ -diff --color -ruNp a/kex.c b/kex.c ---- a/kex.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/kex.c 2024-08-28 12:35:41.249432103 +0200 -@@ -303,17 +303,37 @@ static int - kex_compose_ext_info_server(struct ssh *ssh, struct sshbuf *m) - { - int r; -+ int have_key = 0; -+ int ext_count = 2; -+ -+#ifdef GSSAPI -+ /* -+ * Currently GSS KEX don't provide host keys as optional message, so -+ * no reasons to announce the publickey-hostbound extension -+ */ -+ if (ssh->kex->gss == NULL) -+ have_key = 1; -+#endif -+ ext_count += have_key; -+ - - if (ssh->kex->server_sig_algs == NULL && - (ssh->kex->server_sig_algs = sshkey_alg_list(0, 1, 1, ',')) == NULL) - return SSH_ERR_ALLOC_FAIL; -- if ((r = sshbuf_put_u32(m, 3)) != 0 || -+ if ((r = sshbuf_put_u32(m, ext_count)) != 0 || - (r = sshbuf_put_cstring(m, "server-sig-algs")) != 0 || -- (r = sshbuf_put_cstring(m, ssh->kex->server_sig_algs)) != 0 || -- (r = sshbuf_put_cstring(m, -- "publickey-hostbound@openssh.com")) != 0 || -- (r = sshbuf_put_cstring(m, "0")) != 0 || -- (r = sshbuf_put_cstring(m, "ping@openssh.com")) != 0 || -+ (r = sshbuf_put_cstring(m, ssh->kex->server_sig_algs)) != 0) { -+ error_fr(r, "compose"); -+ return r; -+ } -+ if (have_key) { -+ if ((r = sshbuf_put_cstring(m, "publickey-hostbound@openssh.com")) != 0 || -+ (r = sshbuf_put_cstring(m, "0")) != 0) { -+ error_fr(r, "compose"); -+ return r; -+ } -+ } -+ if ((r = sshbuf_put_cstring(m, "ping@openssh.com")) != 0 || - (r = sshbuf_put_cstring(m, "0")) != 0) { - error_fr(r, "compose"); - return r; -@@ -737,6 +737,9 @@ kex_free(struct kex *kex) - sshbuf_free(kex->server_version); - sshbuf_free(kex->client_pub); - sshbuf_free(kex->session_id); -+#ifdef GSSAPI -+ free(kex->gss_host); -+#endif /* GSSAPI */ - sshbuf_free(kex->initial_sig); - sshkey_free(kex->initial_hostkey); - free(kex->failed_choice); -diff --color -ruNp a/kexdh.c b/kexdh.c ---- a/kexdh.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/kexdh.c 2024-08-28 12:35:41.249432103 +0200 -@@ -49,13 +49,23 @@ kex_dh_keygen(struct kex *kex) - { - switch (kex->kex_type) { - case KEX_DH_GRP1_SHA1: -+#ifdef GSSAPI -+ case KEX_GSS_GRP1_SHA1: -+#endif - kex->dh = dh_new_group1(); - break; - case KEX_DH_GRP14_SHA1: - case KEX_DH_GRP14_SHA256: -+#ifdef GSSAPI -+ case KEX_GSS_GRP14_SHA1: -+ case KEX_GSS_GRP14_SHA256: -+#endif - kex->dh = dh_new_group14(); - break; - case KEX_DH_GRP16_SHA512: -+#ifdef GSSAPI -+ case KEX_GSS_GRP16_SHA512: -+#endif - kex->dh = dh_new_group16(); - break; - case KEX_DH_GRP18_SHA512: -diff --color -ruNp a/kexgen.c b/kexgen.c ---- a/kexgen.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/kexgen.c 2024-08-28 12:35:41.249432103 +0200 -@@ -44,7 +44,7 @@ - static int input_kex_gen_init(int, u_int32_t, struct ssh *); - static int input_kex_gen_reply(int type, u_int32_t seq, struct ssh *ssh); - --static int -+int - kex_gen_hash( - int hash_alg, - const struct sshbuf *client_version, -diff --color -ruNp a/kexgssc.c b/kexgssc.c ---- a/kexgssc.c 1970-01-01 01:00:00.000000000 +0100 -+++ b/kexgssc.c 2024-08-28 12:35:41.250432122 +0200 -@@ -0,0 +1,612 @@ -+/* -+ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. -+ * -+ * Redistribution and use in source and binary forms, with or without -+ * modification, are permitted provided that the following conditions -+ * are met: -+ * 1. Redistributions of source code must retain the above copyright -+ * notice, this list of conditions and the following disclaimer. -+ * 2. Redistributions in binary form must reproduce the above copyright -+ * notice, this list of conditions and the following disclaimer in the -+ * documentation and/or other materials provided with the distribution. -+ * -+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR -+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -+ */ -+ -+#include "includes.h" -+ -+#if defined(GSSAPI) && defined(WITH_OPENSSL) -+ -+#include "includes.h" -+ -+#include -+#include -+ -+#include -+ -+#include "xmalloc.h" -+#include "sshbuf.h" -+#include "ssh2.h" -+#include "sshkey.h" -+#include "cipher.h" -+#include "kex.h" -+#include "log.h" -+#include "packet.h" -+#include "dh.h" -+#include "digest.h" -+#include "ssherr.h" -+ -+#include "ssh-gss.h" -+ -+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) { -+ ssh_gssapi_delete_ctx(&ctxt); -+ 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) { -+ u_char *tmp = NULL; -+ size_t tmp_len = 0; -+ -+ debug("Received KEXGSS_HOSTKEY"); -+ if (server_host_key_blob) -+ fatal("Server host key received more than once"); -+ if ((r = sshpkt_get_string(ssh, &tmp, &tmp_len)) != 0) -+ fatal("Failed to read server host key: %s", ssh_err(r)); -+ if ((server_host_key_blob = sshbuf_from(tmp, tmp_len)) == NULL) -+ fatal("sshbuf_from failed"); -+ } -+ } 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) { -+ u_char *tmp = NULL; -+ size_t tmp_len = 0; -+ -+ debug("Received KEXGSS_HOSTKEY"); -+ if (server_host_key_blob) -+ fatal("Server host key received more than once"); -+ if ((r = sshpkt_get_string(ssh, &tmp, &tmp_len)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ if ((server_host_key_blob = sshbuf_from(tmp, tmp_len)) == NULL) -+ fatal("sshbuf_from failed"); -+ } -+ } 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 --color -ruNp a/kexgsss.c b/kexgsss.c ---- a/kexgsss.c 1970-01-01 01:00:00.000000000 +0100 -+++ b/kexgsss.c 2024-08-28 12:35:41.250432122 +0200 -@@ -0,0 +1,482 @@ -+/* -+ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. -+ * -+ * Redistribution and use in source and binary forms, with or without -+ * modification, are permitted provided that the following conditions -+ * are met: -+ * 1. Redistributions of source code must retain the above copyright -+ * notice, this list of conditions and the following disclaimer. -+ * 2. Redistributions in binary form must reproduce the above copyright -+ * notice, this list of conditions and the following disclaimer in the -+ * documentation and/or other materials provided with the distribution. -+ * -+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR -+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -+ */ -+ -+#include "includes.h" -+ -+#if defined(GSSAPI) && defined(WITH_OPENSSL) -+ -+#include -+ -+#include -+#include -+ -+#include "xmalloc.h" -+#include "sshbuf.h" -+#include "ssh2.h" -+#include "sshkey.h" -+#include "cipher.h" -+#include "kex.h" -+#include "log.h" -+#include "packet.h" -+#include "dh.h" -+#include "ssh-gss.h" -+#include "monitor_wrap.h" -+#include "misc.h" /* servconf.h needs misc.h for struct ForwardOptions */ -+#include "servconf.h" -+#include "ssh-gss.h" -+#include "digest.h" -+#include "ssherr.h" -+ -+extern ServerOptions options; -+ -+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 = {0, NULL}, 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(mm_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 (gssbuf.value != 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 */ -+ -+ /* Calculate the hash early so we can free the -+ * client_pubkey, which has reference to the parent -+ * buffer state->incoming_packet -+ */ -+ 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; -+ -+ sshbuf_free(client_pubkey); -+ client_pubkey = NULL; -+ -+ 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 = mm_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 (gssbuf.value == 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"); -+ -+ if (GSS_ERROR(mm_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(mm_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 = mm_choose_dh(min, nbits, max); -+ if (kex->dh == NULL) { -+ sshpkt_disconnect(ssh, "Protocol error: no matching group found"); -+ fatal("Protocol error: no matching group found"); -+ } -+ -+ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 || -+ (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 || -+ (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 || -+ (r = sshpkt_send(ssh)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ -+ if ((r = ssh_packet_write_wait(ssh)) != 0) -+ fatal("ssh_packet_write_wait: %s", ssh_err(r)); -+ -+ /* Compute our exchange value in parallel with the client */ -+ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) -+ 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 = mm_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(mm_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 --color -ruNp a/kex.h b/kex.h ---- a/kex.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/kex.h 2024-08-28 12:35:41.249432103 +0200 -@@ -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 - }; - -@@ -164,6 +173,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 *); -@@ -189,8 +204,10 @@ int kex_hash_from_name(const char *); - int kex_nid_from_name(const char *); - int kex_names_valid(const char *); - char *kex_alg_list(char); -+char *kex_gss_alg_list(char); - char *kex_names_cat(const char *, const char *); - int kex_has_any_alg(const char *, const char *); -+int kex_gss_names_valid(const char *); - int kex_assemble_names(char **, const char *, const char *); - void kex_proposal_populate_entries(struct ssh *, char *prop[PROPOSAL_MAX], - const char *, const char *, const char *, const char *, const char *); -@@ -224,6 +241,12 @@ int kexgex_client(struct ssh *); - int kexgex_server(struct ssh *); - int kex_gen_client(struct ssh *); - int kex_gen_server(struct ssh *); -+#if defined(GSSAPI) && defined(WITH_OPENSSL) -+int kexgssgex_client(struct ssh *); -+int kexgssgex_server(struct ssh *); -+int kexgss_client(struct ssh *); -+int kexgss_server(struct ssh *); -+#endif - - int kex_dh_keypair(struct kex *); - int kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **, -@@ -256,6 +279,12 @@ int kexgex_hash(int, const struct sshbu - const BIGNUM *, const u_char *, size_t, - u_char *, size_t *); - -+int kex_gen_hash(int hash_alg, const struct sshbuf *client_version, -+ const struct sshbuf *server_version, const struct sshbuf *client_kexinit, -+ const struct sshbuf *server_kexinit, const struct sshbuf *server_host_key_blob, -+ const struct sshbuf *client_pub, const struct sshbuf *server_pub, -+ const struct sshbuf *shared_secret, u_char *hash, size_t *hashlen); -+ - void kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE]) - __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) - __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); -diff --color -ruNp a/kex-names.c b/kex-names.c ---- a/kex-names.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/kex-names.c 2024-08-28 12:35:41.249432103 +0200 -@@ -45,6 +45,10 @@ - #include "ssherr.h" - #include "xmalloc.h" - -+#ifdef GSSAPI -+#include "ssh-gss.h" -+#endif -+ - struct kexalg { - char *name; - u_int type; -@@ -83,15 +87,28 @@ static const struct kexalg kexalgs[] = { - #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */ - { NULL, 0, -1, -1}, - }; -+static const struct kexalg gss_kexalgs[] = { -+#ifdef GSSAPI -+ { KEX_GSS_GEX_SHA1_ID, KEX_GSS_GEX_SHA1, 0, SSH_DIGEST_SHA1 }, -+ { KEX_GSS_GRP1_SHA1_ID, KEX_GSS_GRP1_SHA1, 0, SSH_DIGEST_SHA1 }, -+ { KEX_GSS_GRP14_SHA1_ID, KEX_GSS_GRP14_SHA1, 0, SSH_DIGEST_SHA1 }, -+ { KEX_GSS_GRP14_SHA256_ID, KEX_GSS_GRP14_SHA256, 0, SSH_DIGEST_SHA256 }, -+ { KEX_GSS_GRP16_SHA512_ID, KEX_GSS_GRP16_SHA512, 0, SSH_DIGEST_SHA512 }, -+ { KEX_GSS_NISTP256_SHA256_ID, KEX_GSS_NISTP256_SHA256, -+ NID_X9_62_prime256v1, SSH_DIGEST_SHA256 }, -+ { KEX_GSS_C25519_SHA256_ID, KEX_GSS_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, -+#endif -+ { NULL, 0, -1, -1}, -+}; - --char * --kex_alg_list(char sep) -+static char * -+kex_alg_list_internal(char sep, const struct kexalg *algs) - { - char *ret = NULL, *tmp; - size_t nlen, rlen = 0; - const struct kexalg *k; - -- for (k = kexalgs; k->name != NULL; k++) { -+ for (k = algs; k->name != NULL; k++) { - if (ret != NULL) - ret[rlen++] = sep; - nlen = strlen(k->name); -@@ -106,6 +123,18 @@ kex_alg_list(char sep) - return ret; - } - -+char * -+kex_alg_list(char sep) -+{ -+ return kex_alg_list_internal(sep, kexalgs); -+} -+ -+char * -+kex_gss_alg_list(char sep) -+{ -+ return kex_alg_list_internal(sep, gss_kexalgs); -+} -+ - static const struct kexalg * - kex_alg_by_name(const char *name) - { -@@ -115,6 +144,10 @@ kex_alg_by_name(const char *name) - if (strcmp(k->name, name) == 0) - return k; - } -+ for (k = gss_kexalgs; k->name != NULL; k++) { -+ if (strncmp(k->name, name, strlen(k->name)) == 0) -+ return k; -+ } - return NULL; - } - -@@ -328,3 +361,26 @@ kex_assemble_names(char **listp, const c - free(ret); - return r; - } -+ -+/* Validate GSS KEX method name list */ -+int -+kex_gss_names_valid(const char *names) -+{ -+ char *s, *cp, *p; -+ -+ if (names == NULL || *names == '\0') -+ return 0; -+ s = cp = xstrdup(names); -+ for ((p = strsep(&cp, ",")); p && *p != '\0'; -+ (p = strsep(&cp, ","))) { -+ if (strncmp(p, "gss-", 4) != 0 -+ || kex_alg_by_name(p) == NULL) { -+ error("Unsupported KEX algorithm \"%.100s\"", p); -+ free(s); -+ return 0; -+ } -+ } -+ debug3("gss kex names ok: [%s]", names); -+ free(s); -+ return 1; -+} -diff --color -ruNp a/Makefile.in b/Makefile.in ---- a/Makefile.in 2024-08-28 12:35:01.200659705 +0200 -+++ b/Makefile.in 2024-08-28 12:35:41.244432006 +0200 -@@ -114,6 +114,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ - kex.o kex-names.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ - kexgexc.o kexgexs.o \ - kexsntrup761x25519.o sntrup761.o kexgen.o \ -+ kexgssc.o \ - sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ - sshbuf-io.o - -@@ -135,7 +136,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rh - auth2-chall.o groupaccess.o \ - auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ - auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \ -- monitor.o monitor_wrap.o auth-krb5.o \ -+ monitor.o monitor_wrap.o auth-krb5.o kexgsss.o \ - auth2-gss.o gss-serv.o gss-serv-krb5.o \ - loginrec.o auth-pam.o auth-shadow.o auth-sia.o \ - sftp-server.o sftp-common.o \ -@@ -529,7 +530,7 @@ regress-prep: - ln -s `cd $(srcdir) && pwd`/regress/Makefile `pwd`/regress/Makefile - - REGRESSLIBS=libssh.a $(LIBCOMPAT) --TESTLIBS=$(LIBS) $(CHANNELLIBS) -+TESTLIBS=$(LIBS) $(CHANNELLIBS) $(GSSLIBS) - - regress/modpipe$(EXEEXT): $(srcdir)/regress/modpipe.c $(REGRESSLIBS) - $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(srcdir)/regress/modpipe.c \ -diff --color -ruNp a/monitor.c b/monitor.c ---- a/monitor.c 2024-08-28 12:35:01.192659551 +0200 -+++ b/monitor.c 2024-08-28 12:35:41.251432142 +0200 -@@ -143,6 +143,8 @@ int mm_answer_gss_setup_ctx(struct ssh * - int mm_answer_gss_accept_ctx(struct ssh *, int, struct sshbuf *); - int mm_answer_gss_userok(struct ssh *, int, struct sshbuf *); - int mm_answer_gss_checkmic(struct ssh *, int, struct sshbuf *); -+int mm_answer_gss_sign(struct ssh *, int, struct sshbuf *); -+int mm_answer_gss_updatecreds(struct ssh *, int, struct sshbuf *); - #endif - - #ifdef SSH_AUDIT_EVENTS -@@ -219,11 +221,18 @@ struct mon_table mon_dispatch_proto20[] - {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, - {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok}, - {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic}, -+ {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign}, - #endif - {0, 0, NULL} - }; - - struct mon_table mon_dispatch_postauth20[] = { -+#ifdef GSSAPI -+ {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx}, -+ {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, -+ {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign}, -+ {MONITOR_REQ_GSSUPCREDS, 0, mm_answer_gss_updatecreds}, -+#endif - #ifdef WITH_OPENSSL - {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, - #endif -@@ -292,6 +301,10 @@ monitor_child_preauth(struct ssh *ssh, s - /* Permit requests for moduli and signatures */ - monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); - monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); -+#ifdef GSSAPI -+ /* and for the GSSAPI key exchange */ -+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); -+#endif - - /* The first few requests do not require asynchronous access */ - while (!authenticated) { -@@ -344,8 +357,15 @@ monitor_child_preauth(struct ssh *ssh, s - if (ent->flags & (MON_AUTHDECIDE|MON_ALOG)) { - auth_log(ssh, authenticated, partial, - auth_method, auth_submethod); -- if (!partial && !authenticated) -+ if (!partial && !authenticated) { -+#ifdef GSSAPI -+ /* If gssapi-with-mic failed, MONITOR_REQ_GSSCHECKMIC is disabled. -+ * We have to reenable it to try again for gssapi-keyex */ -+ if (strcmp(auth_method, "gssapi-with-mic") == 0 && options.gss_keyex) -+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); -+#endif - authctxt->failures++; -+ } - if (authenticated || partial) { - auth2_update_session_info(authctxt, - auth_method, auth_submethod); -@@ -413,6 +433,10 @@ monitor_child_postauth(struct ssh *ssh, - monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); - monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); - monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); -+#ifdef GSSAPI -+ /* and for the GSSAPI key exchange */ -+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); -+#endif - - if (auth_opts->permit_pty_flag) { - monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1); -@@ -1793,6 +1817,17 @@ monitor_apply_keystate(struct ssh *ssh, - # ifdef OPENSSL_HAS_ECC - kex->kex[KEX_ECDH_SHA2] = kex_gen_server; - # endif -+# ifdef GSSAPI -+ if (options.gss_keyex) { -+ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; -+ kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; -+ kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server; -+ kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server; -+ kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server; -+ kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server; -+ kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server; -+ } -+# endif - #endif /* WITH_OPENSSL */ - kex->kex[KEX_C25519_SHA256] = kex_gen_server; - kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; -@@ -1885,8 +1920,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, - u_char *p; - int r; - -- if (!options.gss_authentication) -- fatal_f("GSSAPI authentication not enabled"); -+ if (!options.gss_authentication && !options.gss_keyex) -+ fatal_f("GSSAPI not enabled"); - - if ((r = sshbuf_get_string(m, &p, &len)) != 0) - fatal_fr(r, "parse"); -@@ -1918,8 +1953,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh - OM_uint32 flags = 0; /* GSI needs this */ - int r; - -- if (!options.gss_authentication) -- fatal_f("GSSAPI authentication not enabled"); -+ if (!options.gss_authentication && !options.gss_keyex) -+ fatal_f("GSSAPI not enabled"); - - if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0) - fatal_fr(r, "ssh_gssapi_get_buffer_desc"); -@@ -1939,6 +1974,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh - monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0); - monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); - monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); -+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN, 1); - } - return (0); - } -@@ -1950,8 +1986,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, - OM_uint32 ret; - int r; - -- if (!options.gss_authentication) -- fatal_f("GSSAPI authentication not enabled"); -+ if (!options.gss_authentication && !options.gss_keyex) -+ fatal_f("GSSAPI not enabled"); - - if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 || - (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0) -@@ -1977,13 +2013,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, - int - mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) - { -- int r, authenticated; -+ int r, authenticated, kex; - const char *displayname; - -- if (!options.gss_authentication) -- fatal_f("GSSAPI authentication not enabled"); -+ if (!options.gss_authentication && !options.gss_keyex) -+ fatal_f("GSSAPI not enabled"); - -- authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user); -+ if ((r = sshbuf_get_u32(m, &kex)) != 0) -+ fatal_fr(r, "buffer error"); -+ -+ authenticated = authctxt->valid && -+ ssh_gssapi_userok(authctxt->user, authctxt->pw, kex); - - sshbuf_reset(m); - if ((r = sshbuf_put_u32(m, authenticated)) != 0) -@@ -1992,7 +2032,11 @@ mm_answer_gss_userok(struct ssh *ssh, in - debug3_f("sending result %d", authenticated); - mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m); - -- auth_method = "gssapi-with-mic"; -+ if (kex) { -+ auth_method = "gssapi-keyex"; -+ } else { -+ auth_method = "gssapi-with-mic"; -+ } - - if ((displayname = ssh_gssapi_displayname()) != NULL) - auth2_record_info(authctxt, "%s", displayname); -@@ -2000,5 +2044,84 @@ mm_answer_gss_userok(struct ssh *ssh, in - /* Monitor loop will terminate if authenticated */ - return (authenticated); - } -+ -+int -+mm_answer_gss_sign(struct ssh *ssh, int socket, struct sshbuf *m) -+{ -+ gss_buffer_desc data; -+ gss_buffer_desc hash = GSS_C_EMPTY_BUFFER; -+ OM_uint32 major, minor; -+ size_t len; -+ u_char *p = NULL; -+ int r; -+ -+ if (!options.gss_authentication && !options.gss_keyex) -+ fatal_f("GSSAPI not enabled"); -+ -+ if ((r = sshbuf_get_string(m, &p, &len)) != 0) -+ fatal_fr(r, "buffer error"); -+ data.value = p; -+ data.length = len; -+ /* Lengths of SHA-1, SHA-256 and SHA-512 hashes that are used */ -+ if (data.length != 20 && data.length != 32 && data.length != 64) -+ fatal_f("data length incorrect: %d", (int) data.length); -+ -+ /* Save the session ID on the first time around */ -+ if (session_id2_len == 0) { -+ session_id2_len = data.length; -+ session_id2 = xmalloc(session_id2_len); -+ memcpy(session_id2, data.value, session_id2_len); -+ } -+ major = ssh_gssapi_sign(gsscontext, &data, &hash); -+ -+ free(data.value); -+ -+ sshbuf_reset(m); -+ -+ if ((r = sshbuf_put_u32(m, major)) != 0 || -+ (r = sshbuf_put_string(m, hash.value, hash.length)) != 0) -+ fatal_fr(r, "buffer error"); -+ -+ mm_request_send(socket, MONITOR_ANS_GSSSIGN, m); -+ -+ gss_release_buffer(&minor, &hash); -+ -+ /* Turn on getpwnam permissions */ -+ monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1); -+ -+ /* And credential updating, for when rekeying */ -+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSUPCREDS, 1); -+ -+ return (0); -+} -+ -+int -+mm_answer_gss_updatecreds(struct ssh *ssh, int socket, struct sshbuf *m) { -+ ssh_gssapi_ccache store; -+ int r, ok; -+ -+ if (!options.gss_authentication && !options.gss_keyex) -+ fatal_f("GSSAPI not enabled"); -+ -+ if ((r = sshbuf_get_string(m, (u_char **)&store.filename, NULL)) != 0 || -+ (r = sshbuf_get_string(m, (u_char **)&store.envvar, NULL)) != 0 || -+ (r = sshbuf_get_string(m, (u_char **)&store.envval, NULL)) != 0) -+ fatal_fr(r, "buffer error"); -+ -+ ok = ssh_gssapi_update_creds(&store); -+ -+ free(store.filename); -+ free(store.envvar); -+ free(store.envval); -+ -+ sshbuf_reset(m); -+ if ((r = sshbuf_put_u32(m, ok)) != 0) -+ fatal_fr(r, "buffer error"); -+ -+ mm_request_send(socket, MONITOR_ANS_GSSUPCREDS, m); -+ -+ return(0); -+} -+ - #endif /* GSSAPI */ - -diff --color -ruNp a/monitor.h b/monitor.h ---- a/monitor.h 2024-08-28 12:35:01.192659551 +0200 -+++ b/monitor.h 2024-08-28 12:35:41.251432142 +0200 -@@ -67,6 +67,8 @@ enum monitor_reqtype { - MONITOR_REQ_PAM_FREE_CTX = 110, MONITOR_ANS_PAM_FREE_CTX = 111, - MONITOR_REQ_AUDIT_EVENT = 112, MONITOR_REQ_AUDIT_COMMAND = 113, - -+ MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151, -+ MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153, - }; - - struct ssh; -diff --color -ruNp a/monitor_wrap.c b/monitor_wrap.c ---- a/monitor_wrap.c 2024-08-28 12:35:01.192659551 +0200 -+++ b/monitor_wrap.c 2024-08-28 12:35:41.251432142 +0200 -@@ -1075,13 +1075,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss - } - - int --mm_ssh_gssapi_userok(char *user) -+mm_ssh_gssapi_userok(char *user, struct passwd *pw, int kex) - { - struct sshbuf *m; - int r, authenticated = 0; - - if ((m = sshbuf_new()) == NULL) - fatal_f("sshbuf_new failed"); -+ if ((r = sshbuf_put_u32(m, kex)) != 0) -+ fatal_fr(r, "buffer error"); - - mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, m); - mm_request_receive_expect(pmonitor->m_recvfd, -@@ -1094,6 +1096,59 @@ mm_ssh_gssapi_userok(char *user) - debug3_f("user %sauthenticated", authenticated ? "" : "not "); - return (authenticated); - } -+ -+OM_uint32 -+mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash) -+{ -+ struct sshbuf *m; -+ OM_uint32 major; -+ int r; -+ -+ if ((m = sshbuf_new()) == NULL) -+ fatal_f("sshbuf_new failed"); -+ if ((r = sshbuf_put_string(m, data->value, data->length)) != 0) -+ fatal_fr(r, "buffer error"); -+ -+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSIGN, m); -+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSIGN, m); -+ -+ if ((r = sshbuf_get_u32(m, &major)) != 0 || -+ (r = ssh_gssapi_get_buffer_desc(m, hash)) != 0) -+ fatal_fr(r, "buffer error"); -+ -+ sshbuf_free(m); -+ -+ return (major); -+} -+ -+int -+mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *store) -+{ -+ struct sshbuf *m; -+ int r, ok; -+ -+ if ((m = sshbuf_new()) == NULL) -+ fatal_f("sshbuf_new failed"); -+ -+ if ((r = sshbuf_put_cstring(m, -+ store->filename ? store->filename : "")) != 0 || -+ (r = sshbuf_put_cstring(m, -+ store->envvar ? store->envvar : "")) != 0 || -+ (r = sshbuf_put_cstring(m, -+ store->envval ? store->envval : "")) != 0) -+ fatal_fr(r, "buffer error"); -+ -+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUPCREDS, m); -+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUPCREDS, m); -+ -+ if ((r = sshbuf_get_u32(m, &ok)) != 0) -+ fatal_fr(r, "buffer error"); -+ -+ sshbuf_free(m); -+ -+ return (ok); -+} -+ - #endif /* GSSAPI */ - - /* -diff --color -ruNp a/monitor_wrap.h b/monitor_wrap.h ---- a/monitor_wrap.h 2024-08-28 12:35:01.193659570 +0200 -+++ b/monitor_wrap.h 2024-08-28 12:35:41.251432142 +0200 -@@ -67,8 +67,10 @@ void mm_decode_activate_server_options(s - OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID); - OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *, - gss_buffer_desc *, gss_buffer_desc *, OM_uint32 *); --int mm_ssh_gssapi_userok(char *user); -+int mm_ssh_gssapi_userok(char *user, struct passwd *, int kex); - OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); -+OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); -+int mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *); - #endif - - #ifdef USE_PAM -diff --color -ruNp a/packet.c b/packet.c ---- a/packet.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/packet.c 2024-08-28 12:35:41.260432315 +0200 -@@ -1517,6 +1517,29 @@ ssh_packet_read(struct ssh *ssh) - return type; - } - -+/* -+ * Waits until a packet has been received, verifies that its type matches -+ * that given, and gives a fatal error and exits if there is a mismatch. -+ */ -+ -+int -+ssh_packet_read_expect(struct ssh *ssh, u_int expected_type) -+{ -+ int r; -+ u_char type; -+ -+ if ((r = ssh_packet_read_seqnr(ssh, &type, NULL)) != 0) -+ return r; -+ if (type != expected_type) { -+ if ((r = sshpkt_disconnect(ssh, -+ "Protocol error: expected packet type %d, got %d", -+ expected_type, type)) != 0) -+ return r; -+ return SSH_ERR_PROTOCOL_ERROR; -+ } -+ return 0; -+} -+ - static int - ssh_packet_read_poll2_mux(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) - { -diff --color -ruNp a/packet.h b/packet.h ---- a/packet.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/packet.h 2024-08-28 12:35:41.260432315 +0200 -@@ -124,6 +124,7 @@ int ssh_packet_send2_wrapped(struct ssh - int ssh_packet_send2(struct ssh *); - - int ssh_packet_read(struct ssh *); -+int ssh_packet_read_expect(struct ssh *, u_int type); - int ssh_packet_read_poll2(struct ssh *, u_char *, u_int32_t *seqnr_p); - int ssh_packet_process_incoming(struct ssh *, const char *buf, u_int len); - int ssh_packet_process_read(struct ssh *, int); -diff --color -ruNp a/readconf.c b/readconf.c ---- a/readconf.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/readconf.c 2024-08-28 12:35:41.253432180 +0200 -@@ -70,6 +70,7 @@ - #include "uidswap.h" - #include "myproposal.h" - #include "digest.h" -+#include "ssh-gss.h" - - /* Format of the configuration file: - -@@ -164,6 +165,8 @@ typedef enum { - oClearAllForwardings, oNoHostAuthenticationForLocalhost, - oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, - oAddressFamily, oGssAuthentication, oGssDelegateCreds, -+ oGssTrustDns, oGssKeyEx, oGssClientIdentity, oGssRenewalRekey, -+ oGssServerIdentity, oGssKexAlgorithms, - oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, - oSendEnv, oSetEnv, oControlPath, oControlMaster, oControlPersist, - oHashKnownHosts, -@@ -210,10 +213,22 @@ static struct { - /* Sometimes-unsupported options */ - #if defined(GSSAPI) - { "gssapiauthentication", oGssAuthentication }, -+ { "gssapikeyexchange", oGssKeyEx }, - { "gssapidelegatecredentials", oGssDelegateCreds }, -+ { "gssapitrustdns", oGssTrustDns }, -+ { "gssapiclientidentity", oGssClientIdentity }, -+ { "gssapiserveridentity", oGssServerIdentity }, -+ { "gssapirenewalforcesrekey", oGssRenewalRekey }, -+ { "gssapikexalgorithms", oGssKexAlgorithms }, - # else - { "gssapiauthentication", oUnsupported }, -+ { "gssapikeyexchange", oUnsupported }, - { "gssapidelegatecredentials", oUnsupported }, -+ { "gssapitrustdns", oUnsupported }, -+ { "gssapiclientidentity", oUnsupported }, -+ { "gssapiserveridentity", oUnsupported }, -+ { "gssapirenewalforcesrekey", oUnsupported }, -+ { "gssapikexalgorithms", oUnsupported }, - #endif - #ifdef ENABLE_PKCS11 - { "pkcs11provider", oPKCS11Provider }, -@@ -1227,10 +1242,42 @@ parse_time: - intptr = &options->gss_authentication; - goto parse_flag; - -+ case oGssKeyEx: -+ intptr = &options->gss_keyex; -+ goto parse_flag; -+ - case oGssDelegateCreds: - intptr = &options->gss_deleg_creds; - goto parse_flag; - -+ case oGssTrustDns: -+ intptr = &options->gss_trust_dns; -+ goto parse_flag; -+ -+ case oGssClientIdentity: -+ charptr = &options->gss_client_identity; -+ goto parse_string; -+ -+ case oGssServerIdentity: -+ charptr = &options->gss_server_identity; -+ goto parse_string; -+ -+ case oGssRenewalRekey: -+ intptr = &options->gss_renewal_rekey; -+ goto parse_flag; -+ -+ case oGssKexAlgorithms: -+ arg = argv_next(&ac, &av); -+ if (!arg || *arg == '\0') -+ fatal("%.200s line %d: Missing argument.", -+ filename, linenum); -+ if (!kex_gss_names_valid(arg)) -+ fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.", -+ filename, linenum, arg ? arg : ""); -+ if (*activep && options->gss_kex_algorithms == NULL) -+ options->gss_kex_algorithms = xstrdup(arg); -+ break; -+ - case oBatchMode: - intptr = &options->batch_mode; - goto parse_flag; -@@ -2542,7 +2589,13 @@ initialize_options(Options * options) - options->fwd_opts.streamlocal_bind_unlink = -1; - options->pubkey_authentication = -1; - options->gss_authentication = -1; -+ options->gss_keyex = -1; - options->gss_deleg_creds = -1; -+ options->gss_trust_dns = -1; -+ options->gss_renewal_rekey = -1; -+ options->gss_client_identity = NULL; -+ options->gss_server_identity = NULL; -+ options->gss_kex_algorithms = NULL; - options->password_authentication = -1; - options->kbd_interactive_authentication = -1; - options->kbd_interactive_devices = NULL; -@@ -2705,8 +2758,18 @@ fill_default_options(Options * options) - options->pubkey_authentication = SSH_PUBKEY_AUTH_ALL; - if (options->gss_authentication == -1) - options->gss_authentication = 0; -+ if (options->gss_keyex == -1) -+ options->gss_keyex = 0; - if (options->gss_deleg_creds == -1) - options->gss_deleg_creds = 0; -+ if (options->gss_trust_dns == -1) -+ options->gss_trust_dns = 0; -+ if (options->gss_renewal_rekey == -1) -+ options->gss_renewal_rekey = 0; -+#ifdef GSSAPI -+ if (options->gss_kex_algorithms == NULL) -+ options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX); -+#endif - if (options->password_authentication == -1) - options->password_authentication = 1; - if (options->kbd_interactive_authentication == -1) -@@ -3533,7 +3596,14 @@ dump_client_config(Options *o, const cha - dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports); - #ifdef GSSAPI - dump_cfg_fmtint(oGssAuthentication, o->gss_authentication); -+ dump_cfg_fmtint(oGssKeyEx, o->gss_keyex); - dump_cfg_fmtint(oGssDelegateCreds, o->gss_deleg_creds); -+ dump_cfg_fmtint(oGssTrustDns, o->gss_trust_dns); -+ dump_cfg_fmtint(oGssRenewalRekey, o->gss_renewal_rekey); -+ dump_cfg_string(oGssClientIdentity, o->gss_client_identity); -+ dump_cfg_string(oGssServerIdentity, o->gss_server_identity); -+ dump_cfg_string(oGssKexAlgorithms, o->gss_kex_algorithms ? -+ o->gss_kex_algorithms : GSS_KEX_DEFAULT_KEX); - #endif /* GSSAPI */ - dump_cfg_fmtint(oHashKnownHosts, o->hash_known_hosts); - dump_cfg_fmtint(oHostbasedAuthentication, o->hostbased_authentication); -diff --color -ruNp a/readconf.h b/readconf.h ---- a/readconf.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/readconf.h 2024-08-28 12:35:41.254432199 +0200 -@@ -40,7 +40,13 @@ typedef struct { - int pubkey_authentication; /* Try ssh2 pubkey authentication. */ - int hostbased_authentication; /* ssh2's rhosts_rsa */ - int gss_authentication; /* Try GSS authentication */ -+ int gss_keyex; /* Try GSS key exchange */ - int gss_deleg_creds; /* Delegate GSS credentials */ -+ int gss_trust_dns; /* Trust DNS for GSS canonicalization */ -+ int gss_renewal_rekey; /* Credential renewal forces rekey */ -+ char *gss_client_identity; /* Principal to initiate GSSAPI with */ -+ char *gss_server_identity; /* GSSAPI target principal */ -+ char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */ - int password_authentication; /* Try password - * authentication. */ - int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ -diff --color -ruNp a/servconf.c b/servconf.c ---- a/servconf.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/servconf.c 2024-08-28 12:35:41.255432218 +0200 -@@ -68,6 +68,7 @@ - #include "auth.h" - #include "myproposal.h" - #include "digest.h" -+#include "ssh-gss.h" - - #if !defined(SSHD_PAM_SERVICE) - # define SSHD_PAM_SERVICE "sshd" -@@ -137,8 +138,11 @@ initialize_server_options(ServerOptions - options->kerberos_ticket_cleanup = -1; - options->kerberos_get_afs_token = -1; - options->gss_authentication=-1; -+ options->gss_keyex = -1; - options->gss_cleanup_creds = -1; - options->gss_strict_acceptor = -1; -+ options->gss_store_rekey = -1; -+ options->gss_kex_algorithms = NULL; - options->password_authentication = -1; - options->kbd_interactive_authentication = -1; - options->permit_empty_passwd = -1; -@@ -376,10 +380,18 @@ fill_default_server_options(ServerOption - options->kerberos_get_afs_token = 0; - if (options->gss_authentication == -1) - options->gss_authentication = 0; -+ if (options->gss_keyex == -1) -+ options->gss_keyex = 0; - if (options->gss_cleanup_creds == -1) - options->gss_cleanup_creds = 1; - if (options->gss_strict_acceptor == -1) - options->gss_strict_acceptor = 1; -+ if (options->gss_store_rekey == -1) -+ options->gss_store_rekey = 0; -+#ifdef GSSAPI -+ if (options->gss_kex_algorithms == NULL) -+ options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX); -+#endif - if (options->password_authentication == -1) - options->password_authentication = 1; - if (options->kbd_interactive_authentication == -1) -@@ -558,6 +570,7 @@ typedef enum { - sPerSourcePenalties, sPerSourcePenaltyExemptList, - sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, - sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, -+ sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey, - sAcceptEnv, sSetEnv, sPermitTunnel, - sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, - sUsePrivilegeSeparation, sAllowAgentForwarding, -@@ -643,12 +656,22 @@ static struct { - #ifdef GSSAPI - { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL }, - { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL }, -+ { "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL }, - { "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL }, -+ { "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL }, -+ { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL }, -+ { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL }, - #else - { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, - { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, -+ { "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL }, - { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL }, -+ { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL }, -+ { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL }, -+ { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL }, - #endif -+ { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL }, -+ { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL }, - { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL }, - { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, - { "challengeresponseauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, /* alias */ -@@ -1585,6 +1608,10 @@ process_server_config_line_depth(ServerO - intptr = &options->gss_authentication; - goto parse_flag; - -+ case sGssKeyEx: -+ intptr = &options->gss_keyex; -+ goto parse_flag; -+ - case sGssCleanupCreds: - intptr = &options->gss_cleanup_creds; - goto parse_flag; -@@ -1593,6 +1620,22 @@ process_server_config_line_depth(ServerO - intptr = &options->gss_strict_acceptor; - goto parse_flag; - -+ case sGssStoreRekey: -+ intptr = &options->gss_store_rekey; -+ goto parse_flag; -+ -+ case sGssKexAlgorithms: -+ arg = argv_next(&ac, &av); -+ if (!arg || *arg == '\0') -+ fatal("%.200s line %d: Missing argument.", -+ filename, linenum); -+ if (!kex_gss_names_valid(arg)) -+ fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.", -+ filename, linenum, arg ? arg : ""); -+ if (*activep && options->gss_kex_algorithms == NULL) -+ options->gss_kex_algorithms = xstrdup(arg); -+ break; -+ - case sPasswordAuthentication: - intptr = &options->password_authentication; - goto parse_flag; -@@ -3178,6 +3221,10 @@ dump_config(ServerOptions *o) - #ifdef GSSAPI - dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); - dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); -+ dump_cfg_fmtint(sGssKeyEx, o->gss_keyex); -+ dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor); -+ dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey); -+ dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms); - #endif - dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); - dump_cfg_fmtint(sKbdInteractiveAuthentication, -diff --color -ruNp a/servconf.h b/servconf.h ---- a/servconf.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/servconf.h 2024-08-28 12:35:41.255432218 +0200 -@@ -149,8 +149,11 @@ typedef struct { - int kerberos_get_afs_token; /* If true, try to get AFS token if - * authenticated with Kerberos. */ - int gss_authentication; /* If true, permit GSSAPI authentication */ -+ int gss_keyex; /* If true, permit GSSAPI key exchange */ - int gss_cleanup_creds; /* If true, destroy cred cache on logout */ - int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */ -+ int gss_store_rekey; -+ char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */ - int password_authentication; /* If true, permit password - * authentication. */ - int kbd_interactive_authentication; /* If true, permit */ -diff --color -ruNp a/session.c b/session.c ---- a/session.c 2024-08-28 12:35:01.197659647 +0200 -+++ b/session.c 2024-08-28 12:35:41.255432218 +0200 -@@ -2674,13 +2674,19 @@ do_cleanup(struct ssh *ssh, Authctxt *au - - #ifdef KRB5 - if (options.kerberos_ticket_cleanup && -- authctxt->krb5_ctx) -+ authctxt->krb5_ctx) { -+ temporarily_use_uid(authctxt->pw); - krb5_cleanup_proc(authctxt); -+ restore_uid(); -+ } - #endif - - #ifdef GSSAPI -- if (options.gss_cleanup_creds) -+ if (options.gss_cleanup_creds) { -+ temporarily_use_uid(authctxt->pw); - ssh_gssapi_cleanup_creds(); -+ restore_uid(); -+ } - #endif - - /* remove agent socket */ -diff --color -ruNp a/ssh.1 b/ssh.1 ---- a/ssh.1 2024-08-28 12:35:01.207659840 +0200 -+++ b/ssh.1 2024-08-28 12:35:41.256432238 +0200 -@@ -536,7 +536,13 @@ For full details of the options listed b - .It GatewayPorts - .It GlobalKnownHostsFile - .It GSSAPIAuthentication -+.It GSSAPIKeyExchange -+.It GSSAPIClientIdentity - .It GSSAPIDelegateCredentials -+.It GSSAPIKexAlgorithms -+.It GSSAPIRenewalForcesRekey -+.It GSSAPIServerIdentity -+.It GSSAPITrustDns - .It HashKnownHosts - .It Host - .It HostbasedAcceptedAlgorithms -@@ -624,6 +630,8 @@ flag), - (supported message integrity codes), - .Ar kex - (key exchange algorithms), -+.Ar kex-gss -+(GSSAPI key exchange algorithms), - .Ar key - (key types), - .Ar key-ca-sign -diff --color -ruNp a/ssh.c b/ssh.c ---- a/ssh.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/ssh.c 2024-08-28 12:35:41.256432238 +0200 -@@ -827,6 +827,8 @@ main(int ac, char **av) - else if (strcmp(optarg, "kex") == 0 || - strcasecmp(optarg, "KexAlgorithms") == 0) - cp = kex_alg_list('\n'); -+ else if (strcmp(optarg, "kex-gss") == 0) -+ cp = kex_gss_alg_list('\n'); - else if (strcmp(optarg, "key") == 0) - cp = sshkey_alg_list(0, 0, 0, '\n'); - else if (strcmp(optarg, "key-cert") == 0) -@@ -857,8 +859,8 @@ main(int ac, char **av) - } else if (strcmp(optarg, "help") == 0) { - cp = xstrdup( - "cipher\ncipher-auth\ncompression\nkex\n" -- "key\nkey-cert\nkey-plain\nkey-sig\nmac\n" -- "protocol-version\nsig"); -+ "kex-gss\nkey\nkey-cert\nkey-plain\n" -+ "key-sig\nmac\nprotocol-version\nsig"); - } - if (cp == NULL) - fatal("Unsupported query \"%s\"", optarg); -diff --color -ruNp a/ssh_config b/ssh_config ---- a/ssh_config 2024-08-28 12:35:01.216660014 +0200 -+++ b/ssh_config 2024-08-28 12:35:41.256432238 +0200 -@@ -24,6 +24,8 @@ - # HostbasedAuthentication no - # GSSAPIAuthentication no - # GSSAPIDelegateCredentials no -+# GSSAPIKeyExchange no -+# GSSAPITrustDNS no - # BatchMode no - # CheckHostIP no - # AddressFamily any -diff --color -ruNp a/ssh_config.5 b/ssh_config.5 ---- a/ssh_config.5 2024-07-01 06:36:28.000000000 +0200 -+++ b/ssh_config.5 2024-08-28 12:35:41.257432257 +0200 -@@ -938,10 +938,68 @@ The default is - Specifies whether user authentication based on GSSAPI is allowed. - The default is - .Cm no . -+.It Cm GSSAPIClientIdentity -+If set, specifies the GSSAPI client identity that ssh should use when -+connecting to the server. The default is unset, which means that the default -+identity will be used. - .It Cm GSSAPIDelegateCredentials - Forward (delegate) credentials to the server. - The default is - .Cm no . -+.It Cm GSSAPIKeyExchange -+Specifies whether key exchange based on GSSAPI may be used. When using -+GSSAPI key exchange the server need not have a host key. -+The default is -+.Dq no . -+.It Cm GSSAPIRenewalForcesRekey -+If set to -+.Dq yes -+then renewal of the client's GSSAPI credentials will force the rekeying of the -+ssh connection. With a compatible server, this will delegate the renewed -+credentials to a session on the server. -+.Pp -+Checks are made to ensure that credentials are only propagated when the new -+credentials match the old ones on the originating client and where the -+receiving server still has the old set in its cache. -+.Pp -+The default is -+.Dq no . -+.Pp -+For this to work -+.Cm GSSAPIKeyExchange -+needs to be enabled in the server and also used by the client. -+.It Cm GSSAPIServerIdentity -+If set, specifies the GSSAPI server identity that ssh should expect when -+connecting to the server. The default is unset, which means that the -+expected GSSAPI server identity will be determined from the target -+hostname. -+.It Cm GSSAPITrustDns -+Set to -+.Dq yes -+to indicate that the DNS is trusted to securely canonicalize -+the name of the host being connected to. If -+.Dq no , -+the hostname entered on the -+command line will be passed untouched to the GSSAPI library. -+The default is -+.Dq no . -+.It Cm GSSAPIKexAlgorithms -+The list of key exchange algorithms that are offered for GSSAPI -+key exchange. Possible values are -+.Bd -literal -offset 3n -+gss-gex-sha1-, -+gss-group1-sha1-, -+gss-group14-sha1-, -+gss-group14-sha256-, -+gss-group16-sha512-, -+gss-nistp256-sha256-, -+gss-curve25519-sha256- -+.Ed -+.Pp -+The default is -+.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-, -+gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1- . -+This option only applies to connections using GSSAPI. - .It Cm HashKnownHosts - Indicates that - .Xr ssh 1 -diff --color -ruNp a/sshconnect2.c b/sshconnect2.c ---- a/sshconnect2.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/sshconnect2.c 2024-08-28 12:35:41.258432276 +0200 -@@ -222,6 +222,11 @@ ssh_kex2(struct ssh *ssh, char *host, st - char *all_key, *hkalgs = NULL; - int r, use_known_hosts_order = 0; - -+#if defined(GSSAPI) && defined(WITH_OPENSSL) -+ char *orig = NULL, *gss = NULL; -+ char *gss_host = NULL; -+#endif -+ - xxx_host = host; - xxx_hostaddr = hostaddr; - xxx_conn_info = cinfo; -@@ -255,6 +260,42 @@ ssh_kex2(struct ssh *ssh, char *host, st - compression_alg_list(options.compression), - hkalgs ? hkalgs : options.hostkeyalgorithms); - -+#if defined(GSSAPI) && defined(WITH_OPENSSL) -+ if (options.gss_keyex) { -+ /* Add the GSSAPI mechanisms currently supported on this -+ * client to the key exchange algorithm proposal */ -+ orig = myproposal[PROPOSAL_KEX_ALGS]; -+ -+ if (options.gss_server_identity) { -+ gss_host = xstrdup(options.gss_server_identity); -+ } else if (options.gss_trust_dns) { -+ gss_host = remote_hostname(ssh); -+ /* Fall back to specified host if we are using proxy command -+ * and can not use DNS on that socket */ -+ if (strcmp(gss_host, "UNKNOWN") == 0) { -+ free(gss_host); -+ gss_host = xstrdup(host); -+ } -+ } else { -+ gss_host = xstrdup(host); -+ } -+ -+ gss = ssh_gssapi_client_mechanisms(gss_host, -+ options.gss_client_identity, options.gss_kex_algorithms); -+ if (gss) { -+ debug("Offering GSSAPI proposal: %s", gss); -+ xasprintf(&myproposal[PROPOSAL_KEX_ALGS], -+ "%s,%s", gss, orig); -+ -+ /* If we've got GSSAPI algorithms, then we also support the -+ * 'null' hostkey, as a last resort */ -+ orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]; -+ xasprintf(&myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS], -+ "%s,null", orig); -+ } -+ } -+#endif -+ - free(hkalgs); - - /* start key exchange */ -@@ -271,14 +312,44 @@ ssh_kex2(struct ssh *ssh, char *host, st - # ifdef OPENSSL_HAS_ECC - ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client; - # endif --#endif -+# ifdef GSSAPI -+ if (options.gss_keyex) { -+ ssh->kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client; -+ ssh->kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_client; -+ ssh->kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_client; -+ ssh->kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_client; -+ ssh->kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_client; -+ ssh->kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_client; -+ ssh->kex->kex[KEX_GSS_C25519_SHA256] = kexgss_client; -+ } -+# endif -+#endif /* WITH_OPENSSL */ - ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; - ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client; - ssh->kex->verify_host_key=&verify_host_key_callback; - -+#if defined(GSSAPI) && defined(WITH_OPENSSL) -+ if (options.gss_keyex) { -+ ssh->kex->gss_deleg_creds = options.gss_deleg_creds; -+ ssh->kex->gss_trust_dns = options.gss_trust_dns; -+ ssh->kex->gss_client = options.gss_client_identity; -+ ssh->kex->gss_host = gss_host; -+ } -+#endif -+ - ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &ssh->kex->done); - kex_proposal_free_entries(myproposal); - -+#if defined(GSSAPI) && defined(WITH_OPENSSL) -+ /* repair myproposal after it was crumpled by the */ -+ /* ext-info removal above */ -+ if (gss) { -+ orig = myproposal[PROPOSAL_KEX_ALGS]; -+ xasprintf(&myproposal[PROPOSAL_KEX_ALGS], -+ "%s,%s", gss, orig); -+ free(gss); -+ } -+#endif - #ifdef DEBUG_KEXDH - /* send 1st encrypted/maced/compressed message */ - if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 || -@@ -368,6 +439,7 @@ static int input_gssapi_response(int typ - static int input_gssapi_token(int type, u_int32_t, struct ssh *); - static int input_gssapi_error(int, u_int32_t, struct ssh *); - static int input_gssapi_errtok(int, u_int32_t, struct ssh *); -+static int userauth_gsskeyex(struct ssh *); - #endif - - void userauth(struct ssh *, char *); -@@ -384,6 +456,11 @@ static char *authmethods_get(void); - - Authmethod authmethods[] = { - #ifdef GSSAPI -+ {"gssapi-keyex", -+ userauth_gsskeyex, -+ NULL, -+ &options.gss_keyex, -+ NULL}, - {"gssapi-with-mic", - userauth_gssapi, - userauth_gssapi_cleanup, -@@ -755,12 +832,32 @@ userauth_gssapi(struct ssh *ssh) - OM_uint32 min; - int r, ok = 0; - gss_OID mech = NULL; -+ char *gss_host = NULL; -+ -+ if (options.gss_server_identity) { -+ gss_host = xstrdup(options.gss_server_identity); -+ } else if (options.gss_trust_dns) { -+ gss_host = remote_hostname(ssh); -+ /* Fall back to specified host if we are using proxy command -+ * and can not use DNS on that socket */ -+ if (strcmp(gss_host, "UNKNOWN") == 0) { -+ free(gss_host); -+ gss_host = xstrdup(authctxt->host); -+ } -+ } else { -+ gss_host = xstrdup(authctxt->host); -+ } - - /* Try one GSSAPI method at a time, rather than sending them all at - * once. */ - - if (authctxt->gss_supported_mechs == NULL) -- gss_indicate_mechs(&min, &authctxt->gss_supported_mechs); -+ if (GSS_ERROR(gss_indicate_mechs(&min, -+ &authctxt->gss_supported_mechs))) { -+ authctxt->gss_supported_mechs = NULL; -+ free(gss_host); -+ return 0; -+ } - - /* Check to see whether the mechanism is usable before we offer it */ - while (authctxt->mech_tried < authctxt->gss_supported_mechs->count && -@@ -769,13 +866,15 @@ userauth_gssapi(struct ssh *ssh) - elements[authctxt->mech_tried]; - /* My DER encoding requires length<128 */ - if (mech->length < 128 && ssh_gssapi_check_mechanism(&gssctxt, -- mech, authctxt->host)) { -+ mech, gss_host, options.gss_client_identity)) { - ok = 1; /* Mechanism works */ - } else { - authctxt->mech_tried++; - } - } - -+ free(gss_host); -+ - if (!ok || mech == NULL) - return 0; - -@@ -1009,6 +1108,55 @@ input_gssapi_error(int type, u_int32_t p - free(lang); - return r; - } -+ -+int -+userauth_gsskeyex(struct ssh *ssh) -+{ -+ struct sshbuf *b = NULL; -+ Authctxt *authctxt = ssh->authctxt; -+ gss_buffer_desc gssbuf; -+ gss_buffer_desc mic = GSS_C_EMPTY_BUFFER; -+ OM_uint32 ms; -+ int r; -+ -+ static int attempt = 0; -+ if (attempt++ >= 1) -+ return (0); -+ -+ if (gss_kex_context == NULL) { -+ debug("No valid Key exchange context"); -+ return (0); -+ } -+ -+ if ((b = sshbuf_new()) == NULL) -+ fatal_f("sshbuf_new failed"); -+ -+ ssh_gssapi_buildmic(b, authctxt->server_user, authctxt->service, -+ "gssapi-keyex", ssh->kex->session_id); -+ -+ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL) -+ fatal_f("sshbuf_mutable_ptr failed"); -+ gssbuf.length = sshbuf_len(b); -+ -+ if (GSS_ERROR(ssh_gssapi_sign(gss_kex_context, &gssbuf, &mic))) { -+ sshbuf_free(b); -+ return (0); -+ } -+ -+ if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_REQUEST)) != 0 || -+ (r = sshpkt_put_cstring(ssh, authctxt->server_user)) != 0 || -+ (r = sshpkt_put_cstring(ssh, authctxt->service)) != 0 || -+ (r = sshpkt_put_cstring(ssh, authctxt->method->name)) != 0 || -+ (r = sshpkt_put_string(ssh, mic.value, mic.length)) != 0 || -+ (r = sshpkt_send(ssh)) != 0) -+ fatal_fr(r, "parsing"); -+ -+ sshbuf_free(b); -+ gss_release_buffer(&ms, &mic); -+ -+ return (1); -+} -+ - #endif /* GSSAPI */ - - static int -diff --color -ruNp a/sshd.c b/sshd.c ---- a/sshd.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/sshd.c 2024-08-28 12:35:41.258432276 +0200 -@@ -1551,7 +1551,8 @@ main(int ac, char **av) - free(fp); - } - accumulate_host_timing_secret(cfg, NULL); -- if (!sensitive_data.have_ssh2_key) { -+ /* The GSSAPI key exchange can run without a host key */ -+ if (!sensitive_data.have_ssh2_key && !options.gss_keyex) { - logit("sshd: no hostkeys available -- exiting."); - exit(1); - } -diff --color -ruNp a/sshd_config b/sshd_config ---- a/sshd_config 2024-08-28 12:35:01.221660110 +0200 -+++ b/sshd_config 2024-08-28 12:35:41.259432296 +0200 -@@ -77,6 +77,8 @@ AuthorizedKeysFile .ssh/authorized_keys - # GSSAPI options - #GSSAPIAuthentication no - #GSSAPICleanupCredentials yes -+#GSSAPIStrictAcceptorCheck yes -+#GSSAPIKeyExchange no - - # Set this to 'yes' to enable PAM authentication, account processing, - # and session processing. If this is enabled, PAM authentication will -diff --color -ruNp a/sshd_config.5 b/sshd_config.5 ---- a/sshd_config.5 2024-08-28 12:35:01.218660052 +0200 -+++ b/sshd_config.5 2024-08-28 12:35:41.259432296 +0200 -@@ -739,6 +739,11 @@ Specifies whether to automatically destr - on logout. - The default is - .Cm yes . -+.It Cm GSSAPIKeyExchange -+Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange -+doesn't rely on ssh keys to verify host identity. -+The default is -+.Cm no . - .It Cm GSSAPIStrictAcceptorCheck - Determines whether to be strict about the identity of the GSSAPI acceptor - a client authenticates against. -@@ -753,6 +758,32 @@ machine's default store. - This facility is provided to assist with operation on multi homed machines. - The default is - .Cm yes . -+.It Cm GSSAPIStoreCredentialsOnRekey -+Controls whether the user's GSSAPI credentials should be updated following a -+successful connection rekeying. This option can be used to accepted renewed -+or updated credentials from a compatible client. The default is -+.Dq no . -+.Pp -+For this to work -+.Cm GSSAPIKeyExchange -+needs to be enabled in the server and also used by the client. -+.It Cm GSSAPIKexAlgorithms -+The list of key exchange algorithms that are accepted by GSSAPI -+key exchange. Possible values are -+.Bd -literal -offset 3n -+gss-gex-sha1-, -+gss-group1-sha1-, -+gss-group14-sha1-, -+gss-group14-sha256-, -+gss-group16-sha512-, -+gss-nistp256-sha256-, -+gss-curve25519-sha256- -+.Ed -+.Pp -+The default is -+.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-, -+gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1- . -+This option only applies to connections using GSSAPI. - .It Cm HostbasedAcceptedAlgorithms - Specifies the signature algorithms that will be accepted for hostbased - authentication as a list of comma-separated patterns. -diff --color -ruNp a/sshd-session.c b/sshd-session.c ---- a/sshd-session.c 2024-08-28 12:35:01.221660110 +0200 -+++ b/sshd-session.c 2024-08-28 12:35:41.263432373 +0200 -@@ -660,8 +660,8 @@ notify_hostkeys(struct ssh *ssh) - } - debug3_f("sent %u hostkeys", nkeys); - if (nkeys == 0) -- fatal_f("no hostkeys"); -- if ((r = sshpkt_send(ssh)) != 0) -+ debug3_f("no hostkeys"); -+ else if ((r = sshpkt_send(ssh)) != 0) - sshpkt_fatal(ssh, r, "%s: send", __func__); - sshbuf_free(buf); - } -@@ -1180,8 +1180,9 @@ main(int ac, char **av) - break; - } - } -- if (!have_key) -- fatal("internal error: monitor received no hostkeys"); -+ /* The GSSAPI key exchange can run without a host key */ -+ if (!have_key && !options.gss_keyex) -+ fatal("internal error: monitor received no hostkeys and GSS KEX is not configured"); - - /* Ensure that umask disallows at least group and world write */ - new_umask = umask(0077) | 0022; -@@ -1462,6 +1463,48 @@ do_ssh2_kex(struct ssh *ssh) - - free(hkalgs); - -+#if defined(GSSAPI) && defined(WITH_OPENSSL) -+ { -+ char *orig; -+ char *gss = NULL; -+ char *newstr = NULL; -+ orig = myproposal[PROPOSAL_KEX_ALGS]; -+ -+ /* -+ * If we don't have a host key, then there's no point advertising -+ * the other key exchange algorithms -+ */ -+ -+ if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]) == 0) -+ orig = NULL; -+ -+ if (options.gss_keyex) -+ gss = ssh_gssapi_server_mechanisms(); -+ else -+ gss = NULL; -+ -+ if (gss && orig) -+ xasprintf(&newstr, "%s,%s", gss, orig); -+ else if (gss) -+ xasprintf(&newstr, "%s,%s", gss, "kex-strict-s-v00@openssh.com"); -+ else if (orig) -+ newstr = orig; -+ -+ /* -+ * If we've got GSSAPI mechanisms, then we've got the 'null' host -+ * key alg, but we can't tell people about it unless its the only -+ * host key algorithm we support -+ */ -+ if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0) -+ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = xstrdup("null"); -+ -+ if (newstr) -+ myproposal[PROPOSAL_KEX_ALGS] = newstr; -+ else -+ fatal("No supported key exchange algorithms"); -+ } -+#endif -+ - /* start key exchange */ - if ((r = kex_setup(ssh, myproposal)) != 0) - fatal_r(r, "kex_setup"); -@@ -1479,7 +1522,18 @@ do_ssh2_kex(struct ssh *ssh) - #ifdef OPENSSL_HAS_ECC - kex->kex[KEX_ECDH_SHA2] = kex_gen_server; - #endif --#endif -+# ifdef GSSAPI -+ if (options.gss_keyex) { -+ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; -+ kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; -+ kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server; -+ kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server; -+ kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server; -+ kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server; -+ kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server; -+ } -+# endif -+#endif /* WITH_OPENSSL */ - kex->kex[KEX_C25519_SHA256] = kex_gen_server; - kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; - kex->load_host_public_key=&get_hostkey_public_by_type; -diff --color -ruNp a/ssh-gss.h b/ssh-gss.h ---- a/ssh-gss.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/ssh-gss.h 2024-08-28 12:35:41.256432238 +0200 -@@ -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); -@@ -108,6 +137,7 @@ OM_uint32 ssh_gssapi_test_oid_supported( - - struct sshbuf; - int ssh_gssapi_get_buffer_desc(struct sshbuf *, gss_buffer_desc *); -+int ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *, gss_buffer_desc *); - - OM_uint32 ssh_gssapi_import_name(Gssctxt *, const char *); - OM_uint32 ssh_gssapi_init_ctx(Gssctxt *, int, -@@ -122,17 +152,33 @@ void ssh_gssapi_delete_ctx(Gssctxt **); - OM_uint32 ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); - void ssh_gssapi_buildmic(struct sshbuf *, const char *, - const char *, const char *, const struct sshbuf *); --int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *); -+int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *, const char *); -+OM_uint32 ssh_gssapi_client_identity(Gssctxt *, const char *); -+int ssh_gssapi_credentials_updated(Gssctxt *); - - /* In the server */ -+typedef int ssh_gssapi_check_fn(Gssctxt **, gss_OID, const char *, -+ const char *); -+char *ssh_gssapi_client_mechanisms(const char *, const char *, const char *); -+char *ssh_gssapi_kex_mechs(gss_OID_set, ssh_gssapi_check_fn *, const char *, -+ const char *, const char *); -+gss_OID ssh_gssapi_id_kex(Gssctxt *, char *, int); -+int ssh_gssapi_server_check_mech(Gssctxt **,gss_OID, const char *, -+ const char *); - OM_uint32 ssh_gssapi_server_ctx(Gssctxt **, gss_OID); --int ssh_gssapi_userok(char *name); -+int ssh_gssapi_userok(char *name, struct passwd *, int kex); - OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); - void ssh_gssapi_do_child(char ***, u_int *); - void ssh_gssapi_cleanup_creds(void); - void ssh_gssapi_storecreds(void); - const char *ssh_gssapi_displayname(void); - -+char *ssh_gssapi_server_mechanisms(void); -+int ssh_gssapi_oid_table_ok(void); -+ -+int ssh_gssapi_update_creds(ssh_gssapi_ccache *store); -+void ssh_gssapi_rekey_creds(void); -+ - #endif /* GSSAPI */ - - #endif /* _SSH_GSS_H */ -diff --color -ruNp a/sshkey.c b/sshkey.c ---- a/sshkey.c 2024-07-01 06:36:28.000000000 +0200 -+++ b/sshkey.c 2024-08-28 12:35:41.260432315 +0200 -@@ -131,6 +131,75 @@ extern const struct sshkey_impl sshkey_x - extern const struct sshkey_impl sshkey_xmss_cert_impl; - #endif - -+static int ssh_gss_equal(const struct sshkey *, const struct sshkey *) -+{ -+ return SSH_ERR_FEATURE_UNSUPPORTED; -+} -+ -+static int ssh_gss_serialize_public(const struct sshkey *, struct sshbuf *, -+ enum sshkey_serialize_rep) -+{ -+ return SSH_ERR_FEATURE_UNSUPPORTED; -+} -+ -+static int ssh_gss_deserialize_public(const char *, struct sshbuf *, -+ struct sshkey *) -+{ -+ return SSH_ERR_FEATURE_UNSUPPORTED; -+} -+ -+static int ssh_gss_serialize_private(const struct sshkey *, struct sshbuf *, -+ enum sshkey_serialize_rep) -+{ -+ return SSH_ERR_FEATURE_UNSUPPORTED; -+} -+ -+static int ssh_gss_deserialize_private(const char *, struct sshbuf *, -+ struct sshkey *) -+{ -+ return SSH_ERR_FEATURE_UNSUPPORTED; -+} -+ -+static int ssh_gss_copy_public(const struct sshkey *, struct sshkey *) -+{ -+ return SSH_ERR_FEATURE_UNSUPPORTED; -+} -+ -+static int ssh_gss_verify(const struct sshkey *, const u_char *, size_t, -+ const u_char *, size_t, const char *, u_int, -+ struct sshkey_sig_details **) -+{ -+ return SSH_ERR_FEATURE_UNSUPPORTED; -+} -+ -+static const struct sshkey_impl_funcs sshkey_gss_funcs = { -+ /* .size = */ NULL, -+ /* .alloc = */ NULL, -+ /* .cleanup = */ NULL, -+ /* .equal = */ ssh_gss_equal, -+ /* .ssh_serialize_public = */ ssh_gss_serialize_public, -+ /* .ssh_deserialize_public = */ ssh_gss_deserialize_public, -+ /* .ssh_serialize_private = */ ssh_gss_serialize_private, -+ /* .ssh_deserialize_private = */ ssh_gss_deserialize_private, -+ /* .generate = */ NULL, -+ /* .copy_public = */ ssh_gss_copy_public, -+ /* .sign = */ NULL, -+ /* .verify = */ ssh_gss_verify, -+}; -+ -+/* The struct is intentionally dummy and has no gss calls */ -+static const struct sshkey_impl sshkey_gss_kex_impl = { -+ /* .name = */ "null", -+ /* .shortname = */ "null", -+ /* .sigalg = */ NULL, -+ /* .type = */ KEY_NULL, -+ /* .nid = */ 0, -+ /* .cert = */ 0, -+ /* .sigonly = */ 0, -+ /* .keybits = */ 0, /* FIXME */ -+ /* .funcs = */ &sshkey_gss_funcs, -+}; -+ - const struct sshkey_impl * const keyimpls[] = { - &sshkey_ed25519_impl, - &sshkey_ed25519_cert_impl, -@@ -169,6 +238,7 @@ const struct sshkey_impl * const keyimpl - &sshkey_xmss_impl, - &sshkey_xmss_cert_impl, - #endif -+ &sshkey_gss_kex_impl, - NULL - }; - -@@ -324,7 +394,7 @@ sshkey_alg_list(int certs_only, int plai - - for (i = 0; keyimpls[i] != NULL; i++) { - impl = keyimpls[i]; -- if (impl->name == NULL) -+ if (impl->name == NULL || impl->type == KEY_NULL) - continue; - if (!include_sigonly && impl->sigonly) - continue; -diff --color -ruNp a/sshkey.h b/sshkey.h ---- a/sshkey.h 2024-07-01 06:36:28.000000000 +0200 -+++ b/sshkey.h 2024-08-28 12:35:41.260432315 +0200 -@@ -71,6 +71,7 @@ enum sshkey_types { - KEY_ECDSA_SK_CERT, - KEY_ED25519_SK, - KEY_ED25519_SK_CERT, -+ KEY_NULL, - KEY_UNSPEC - }; - diff --git a/openssh-8.0p1-pkcs11-uri.patch b/openssh-8.0p1-pkcs11-uri.patch index 9931d92..bdc4722 100644 --- a/openssh-8.0p1-pkcs11-uri.patch +++ b/openssh-8.0p1-pkcs11-uri.patch @@ -1353,9 +1353,17 @@ diff -up openssh-9.6p1/ssh-pkcs11-client.c.pkcs11-uri openssh-9.6p1/ssh-pkcs11-c for (i = 0; i < nkeys; i++) { /* XXX clean up properly instead of fatal() */ if ((r = sshbuf_get_string(msg, &blob, &blen)) != 0 || -diff -up openssh-9.6p1/ssh-pkcs11.c.pkcs11-uri openssh-9.6p1/ssh-pkcs11.c ---- openssh-9.6p1/ssh-pkcs11.c.pkcs11-uri 2023-12-18 15:59:50.000000000 +0100 -+++ openssh-9.6p1/ssh-pkcs11.c 2024-01-12 14:28:09.170975480 +0100 +diff -up openssh-9.9p1/ssh-pkcs11.c.xxx openssh-9.9p1/ssh-pkcs11.c +--- openssh-9.9p1/ssh-pkcs11.c.xxx 2024-10-09 11:56:35.890126144 +0200 ++++ openssh-9.9p1/ssh-pkcs11.c 2024-10-09 11:56:48.528459585 +0200 +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + + #define CRYPTOKI_COMPAT + #include "pkcs11.h" @@ -55,8 +55,8 @@ struct pkcs11_slotinfo { int logged_in; }; @@ -1556,7 +1564,7 @@ diff -up openssh-9.6p1/ssh-pkcs11.c.pkcs11-uri openssh-9.6p1/ssh-pkcs11.c } static RSA_METHOD *rsa_method; -@@ -195,6 +286,56 @@ static EC_KEY_METHOD *ec_key_method; +@@ -195,6 +286,60 @@ static EC_KEY_METHOD *ec_key_method; static int ec_key_idx = 0; #endif /* OPENSSL_HAS_ECC && HAVE_EC_KEY_METHOD_NEW */ @@ -1573,13 +1581,17 @@ diff -up openssh-9.6p1/ssh-pkcs11.c.pkcs11-uri openssh-9.6p1/ssh-pkcs11.c + + /* sanity - is it a RSA key with associated app_data? */ + switch (key->type) { -+ case KEY_RSA: -+ k11 = RSA_get_ex_data(key->rsa, rsa_idx); ++ case KEY_RSA: { ++ const RSA *rsa = EVP_PKEY_get0_RSA(key->pkey); ++ k11 = RSA_get_ex_data(rsa, rsa_idx); + break; ++ } +#ifdef HAVE_EC_KEY_METHOD_NEW -+ case KEY_ECDSA: -+ k11 = EC_KEY_get_ex_data(key->ecdsa, ec_key_idx); ++ case KEY_ECDSA: { ++ const EC_KEY * ecdsa = EVP_PKEY_get0_EC_KEY(key->pkey); ++ k11 = EC_KEY_get_ex_data(ecdsa, ec_key_idx); + break; ++ } +#endif + default: + error("Unknown key type %d", key->type); @@ -1733,9 +1745,9 @@ diff -up openssh-9.6p1/ssh-pkcs11.c.pkcs11-uri openssh-9.6p1/ssh-pkcs11.c + k11->label[label_attrib->ulValueLen] = 0; + } + - RSA_set_method(rsa, rsa_method); - RSA_set_ex_data(rsa, rsa_idx, k11); - return (0); + if (RSA_set_method(rsa, rsa_method) != 1) + fatal_f("RSA_set_method failed"); + if (RSA_set_ex_data(rsa, rsa_idx, k11) != 1) @@ -532,8 +683,8 @@ ecdsa_do_sign(const unsigned char *dgst, return (NULL); } @@ -1766,9 +1778,9 @@ diff -up openssh-9.6p1/ssh-pkcs11.c.pkcs11-uri openssh-9.6p1/ssh-pkcs11.c + k11->label[label_attrib->ulValueLen] = 0; + } + - EC_KEY_set_method(ec, ec_key_method); - EC_KEY_set_ex_data(ec, ec_key_idx, k11); - + if (EC_KEY_set_method(ec, ec_key_method) != 1) + fatal_f("EC_KEY_set_method failed"); + if (EC_KEY_set_ex_data(ec, ec_key_idx, k11) != 1) @@ -622,7 +779,8 @@ pkcs11_ecdsa_wrap(struct pkcs11_provider } #endif /* OPENSSL_HAS_ECC && HAVE_EC_KEY_METHOD_NEW */ @@ -1895,7 +1907,7 @@ diff -up openssh-9.6p1/ssh-pkcs11.c.pkcs11-uri openssh-9.6p1/ssh-pkcs11.c key = sshkey_new(KEY_UNSPEC); @@ -810,7 +970,7 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_ - ec = NULL; /* now owned by key */ + key->flags |= SSHKEY_FLAG_EXT; fail: - for (i = 0; i < 3; i++) @@ -1979,7 +1991,7 @@ diff -up openssh-9.6p1/ssh-pkcs11.c.pkcs11-uri openssh-9.6p1/ssh-pkcs11.c key = sshkey_new(KEY_UNSPEC); @@ -905,7 +1067,7 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_pr - rsa = NULL; /* now owned by key */ + key->flags |= SSHKEY_FLAG_EXT; fail: - for (i = 0; i < 3; i++) diff --git a/openssh-8.7p1-nohostsha1proof.patch b/openssh-8.7p1-nohostsha1proof.patch index abc6aa4..dae0932 100644 --- a/openssh-8.7p1-nohostsha1proof.patch +++ b/openssh-8.7p1-nohostsha1proof.patch @@ -94,47 +94,6 @@ diff -up openssh-8.7p1/monitor.c.sshrsacheck openssh-8.7p1/monitor.c is_proof ? "hostkey proof" : "KEX", siglen); sshbuf_reset(m); -diff -up openssh-8.7p1/regress/cert-userkey.sh.sshrsacheck openssh-8.7p1/regress/cert-userkey.sh ---- openssh-8.7p1/regress/cert-userkey.sh.sshrsacheck 2023-01-25 14:26:52.885963113 +0100 -+++ openssh-8.7p1/regress/cert-userkey.sh 2023-01-25 14:27:25.757219800 +0100 -@@ -7,7 +7,8 @@ rm -f $OBJ/authorized_keys_$USER $OBJ/us - cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak - cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak - --PLAIN_TYPES=`$SSH -Q key-plain | maybe_filter_sk | sed 's/^ssh-dss/ssh-dsa/;s/^ssh-//'` -+#ssh-dss keys are incompatible with DEFAULT crypto policy -+PLAIN_TYPES=`$SSH -Q key-plain | maybe_filter_sk | grep -v 'ssh-dss' | sed 's/^ssh-dss/ssh-dsa/;s/^ssh-//'` - EXTRA_TYPES="" - rsa="" - -diff -up openssh-8.7p1/regress/Makefile.sshrsacheck openssh-8.7p1/regress/Makefile ---- openssh-8.7p1/regress/Makefile.sshrsacheck 2023-01-20 13:07:54.169676051 +0100 -+++ openssh-8.7p1/regress/Makefile 2023-01-20 13:07:54.290677074 +0100 -@@ -2,7 +2,8 @@ - - tests: prep file-tests t-exec unit - --REGRESS_TARGETS= t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 -+#ssh-dss tests will not pass on DEFAULT crypto-policy because of SHA1, skipping -+REGRESS_TARGETS= t1 t2 t3 t4 t5 t7 t8 t9 t10 t11 t12 - - # File based tests - file-tests: $(REGRESS_TARGETS) -diff -up openssh-8.7p1/regress/test-exec.sh.sshrsacheck openssh-8.7p1/regress/test-exec.sh ---- openssh-8.7p1/regress/test-exec.sh.sshrsacheck 2023-01-25 14:24:54.778040819 +0100 -+++ openssh-8.7p1/regress/test-exec.sh 2023-01-25 14:26:39.500858590 +0100 -@@ -581,8 +581,9 @@ maybe_filter_sk() { - fi - } - --SSH_KEYTYPES=`$SSH -Q key-plain | maybe_filter_sk` --SSH_HOSTKEY_TYPES=`$SSH -Q key-plain | maybe_filter_sk` -+#ssh-dss keys are incompatible with DEFAULT crypto policy -+SSH_KEYTYPES=`$SSH -Q key-plain | maybe_filter_sk | grep -v 'ssh-dss'` -+SSH_HOSTKEY_TYPES=`$SSH -Q key-plain | maybe_filter_sk | grep -v 'ssh-dss'` - - for t in ${SSH_KEYTYPES}; do - # generate user key diff -up openssh-8.7p1/regress/unittests/kex/test_kex.c.sshrsacheck openssh-8.7p1/regress/unittests/kex/test_kex.c --- openssh-8.7p1/regress/unittests/kex/test_kex.c.sshrsacheck 2023-01-26 13:34:52.645743677 +0100 +++ openssh-8.7p1/regress/unittests/kex/test_kex.c 2023-01-26 13:36:56.220745823 +0100 diff --git a/openssh-9.0p1-evp-fips-dh.patch b/openssh-9.0p1-evp-fips-dh.patch deleted file mode 100644 index 8c70197..0000000 --- a/openssh-9.0p1-evp-fips-dh.patch +++ /dev/null @@ -1,292 +0,0 @@ -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/dh.c openssh-9.0p1-patched/dh.c ---- openssh-9.0p1/dh.c 2023-05-25 09:24:28.730868316 +0200 -+++ openssh-9.0p1-patched/dh.c 2023-05-25 09:23:44.841379532 +0200 -@@ -37,6 +37,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "dh.h" - #include "pathnames.h" -@@ -290,10 +293,15 @@ - int - dh_gen_key(DH *dh, int need) - { -- int pbits; -- const BIGNUM *dh_p, *pub_key; -+ const BIGNUM *dh_p, *dh_g; -+ BIGNUM *pub_key = NULL, *priv_key = NULL; -+ EVP_PKEY *pkey = NULL; -+ EVP_PKEY_CTX *ctx = NULL; -+ OSSL_PARAM_BLD *param_bld = NULL; -+ OSSL_PARAM *params = NULL; -+ int pbits, r = 0; - -- DH_get0_pqg(dh, &dh_p, NULL, NULL); -+ DH_get0_pqg(dh, &dh_p, NULL, &dh_g); - - if (need < 0 || dh_p == NULL || - (pbits = BN_num_bits(dh_p)) <= 0 || -@@ -301,19 +309,85 @@ - return SSH_ERR_INVALID_ARGUMENT; - if (need < 256) - need = 256; -+ -+ if ((param_bld = OSSL_PARAM_BLD_new()) == NULL || -+ (ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL)) == NULL) { -+ OSSL_PARAM_BLD_free(param_bld); -+ return SSH_ERR_ALLOC_FAIL; -+ } -+ -+ if (OSSL_PARAM_BLD_push_BN(param_bld, -+ OSSL_PKEY_PARAM_FFC_P, dh_p) != 1 || -+ OSSL_PARAM_BLD_push_BN(param_bld, -+ OSSL_PKEY_PARAM_FFC_G, dh_g) != 1) { -+ error_f("Could not set p,q,g parameters"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } - /* - * Pollard Rho, Big step/Little Step attacks are O(sqrt(n)), - * so double requested need here. - */ -- if (!DH_set_length(dh, MINIMUM(need * 2, pbits - 1))) -- return SSH_ERR_LIBCRYPTO_ERROR; -- -- if (DH_generate_key(dh) == 0) -- return SSH_ERR_LIBCRYPTO_ERROR; -- DH_get0_key(dh, &pub_key, NULL); -- if (!dh_pub_is_valid(dh, pub_key)) -- return SSH_ERR_INVALID_FORMAT; -- return 0; -+ if (OSSL_PARAM_BLD_push_int(param_bld, -+ OSSL_PKEY_PARAM_DH_PRIV_LEN, -+ MINIMUM(need * 2, pbits - 1)) != 1 || -+ (params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (EVP_PKEY_fromdata_init(ctx) != 1) { -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (EVP_PKEY_fromdata(ctx, &pkey, -+ EVP_PKEY_KEY_PARAMETERS, params) != 1) { -+ error_f("Failed key generation"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ -+ /* reuse context for key generation */ -+ EVP_PKEY_CTX_free(ctx); -+ ctx = NULL; -+ -+ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL || -+ EVP_PKEY_keygen_init(ctx) != 1) { -+ error_f("Could not create or init context"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (EVP_PKEY_generate(ctx, &pkey) != 1) { -+ error_f("Could not generate keys"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (EVP_PKEY_public_check(ctx) != 1) { -+ error_f("The public key is incorrect"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ -+ if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, -+ &pub_key) != 1 || -+ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, -+ &priv_key) != 1 || -+ DH_set0_key(dh, pub_key, priv_key) != 1) { -+ error_f("Could not set pub/priv keys to DH struct"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ -+ /* transferred */ -+ pub_key = NULL; -+ priv_key = NULL; -+out: -+ OSSL_PARAM_free(params); -+ OSSL_PARAM_BLD_free(param_bld); -+ EVP_PKEY_CTX_free(ctx); -+ EVP_PKEY_free(pkey); -+ BN_clear_free(pub_key); -+ BN_clear_free(priv_key); -+ return r; - } - - DH * -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/kex.c openssh-9.0p1-patched/kex.c ---- openssh-9.0p1/kex.c 2023-05-25 09:24:28.731868327 +0200 -+++ openssh-9.0p1-patched/kex.c 2023-05-25 09:23:44.841379532 +0200 -@@ -1623,3 +1623,47 @@ - return r; - } - -+#ifdef WITH_OPENSSL -+/* -+ * Creates an EVP_PKEY from the given parameters and keys. -+ * The private key can be omitted. -+ */ -+int -+kex_create_evp_dh(EVP_PKEY **pkey, const BIGNUM *p, const BIGNUM *q, -+ const BIGNUM *g, const BIGNUM *pub, const BIGNUM *priv) -+{ -+ OSSL_PARAM_BLD *param_bld = NULL; -+ EVP_PKEY_CTX *ctx = NULL; -+ int r = 0; -+ -+ /* create EVP_PKEY-DH key */ -+ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL)) == NULL || -+ (param_bld = OSSL_PARAM_BLD_new()) == NULL) { -+ error_f("EVP_PKEY_CTX or PARAM_BLD init failed"); -+ r = SSH_ERR_ALLOC_FAIL; -+ goto out; -+ } -+ if (OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, p) != 1 || -+ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_Q, q) != 1 || -+ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, g) != 1 || -+ OSSL_PARAM_BLD_push_BN(param_bld, -+ OSSL_PKEY_PARAM_PUB_KEY, pub) != 1) { -+ error_f("Failed pushing params to OSSL_PARAM_BLD"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (priv != NULL && -+ OSSL_PARAM_BLD_push_BN(param_bld, -+ OSSL_PKEY_PARAM_PRIV_KEY, priv) != 1) { -+ error_f("Failed pushing private key to OSSL_PARAM_BLD"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if ((*pkey = sshkey_create_evp(param_bld, ctx)) == NULL) -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+out: -+ OSSL_PARAM_BLD_free(param_bld); -+ EVP_PKEY_CTX_free(ctx); -+ return r; -+} -+#endif /* WITH_OPENSSL */ -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/kexdh.c openssh-9.0p1-patched/kexdh.c ---- openssh-9.0p1/kexdh.c 2023-05-25 09:24:28.674867692 +0200 -+++ openssh-9.0p1-patched/kexdh.c 2023-05-25 09:25:28.494533889 +0200 -@@ -35,6 +35,10 @@ - - #include "openbsd-compat/openssl-compat.h" - #include -+#include -+#include -+#include -+#include - - #include "sshkey.h" - #include "kex.h" -@@ -83,9 +87,12 @@ - kex_dh_compute_key(struct kex *kex, BIGNUM *dh_pub, struct sshbuf *out) - { - BIGNUM *shared_secret = NULL; -+ const BIGNUM *pub, *priv, *p, *q, *g; -+ EVP_PKEY *pkey = NULL, *dh_pkey = NULL; -+ EVP_PKEY_CTX *ctx = NULL; - u_char *kbuf = NULL; - size_t klen = 0; -- int kout, r; -+ int r = 0; - - #ifdef DEBUG_KEXDH - fprintf(stderr, "dh_pub= "); -@@ -100,24 +107,59 @@ - r = SSH_ERR_MESSAGE_INCOMPLETE; - goto out; - } -- klen = DH_size(kex->dh); -+ -+ DH_get0_key(kex->dh, &pub, &priv); -+ DH_get0_pqg(kex->dh, &p, &q, &g); -+ /* import key */ -+ r = kex_create_evp_dh(&pkey, p, q, g, pub, priv); -+ if (r != 0) { -+ error_f("Could not create EVP_PKEY for dh"); -+ ERR_print_errors_fp(stderr); -+ goto out; -+ } -+ /* import peer key -+ * the parameters should be the same as with pkey -+ */ -+ r = kex_create_evp_dh(&dh_pkey, p, q, g, dh_pub, NULL); -+ if (r != 0) { -+ error_f("Could not import peer key for dh"); -+ ERR_print_errors_fp(stderr); -+ goto out; -+ } -+ -+ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL) { -+ error_f("Could not init EVP_PKEY_CTX for dh"); -+ r = SSH_ERR_ALLOC_FAIL; -+ goto out; -+ } -+ if (EVP_PKEY_derive_init(ctx) != 1 || -+ EVP_PKEY_derive_set_peer(ctx, dh_pkey) != 1 || -+ EVP_PKEY_derive(ctx, NULL, &klen) != 1) { -+ error_f("Could not get key size"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } - if ((kbuf = malloc(klen)) == NULL || - (shared_secret = BN_new()) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } -- if ((kout = DH_compute_key(kbuf, dh_pub, kex->dh)) < 0 || -- BN_bin2bn(kbuf, kout, shared_secret) == NULL) { -+ if (EVP_PKEY_derive(ctx, kbuf, &klen) != 1 || -+ BN_bin2bn(kbuf, klen, shared_secret) == NULL) { -+ error_f("Could not derive key"); - r = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - #ifdef DEBUG_KEXDH -- dump_digest("shared secret", kbuf, kout); -+ dump_digest("shared secret", kbuf, klen); - #endif - r = sshbuf_put_bignum2(out, shared_secret); - out: - freezero(kbuf, klen); - BN_clear_free(shared_secret); -+ EVP_PKEY_free(pkey); -+ EVP_PKEY_free(dh_pkey); -+ EVP_PKEY_CTX_free(ctx); - return r; - } - -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/kex.h openssh-9.0p1-patched/kex.h ---- openssh-9.0p1/kex.h 2023-05-25 09:24:28.725868260 +0200 -+++ openssh-9.0p1-patched/kex.h 2023-05-25 09:23:44.841379532 +0200 -@@ -33,6 +33,9 @@ - # include - # include - # include -+# include -+# include -+# include - # ifdef OPENSSL_HAS_ECC - # include - # else /* OPENSSL_HAS_ECC */ -@@ -283,6 +286,8 @@ - const u_char pub[CURVE25519_SIZE], struct sshbuf *out, int) - __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) - __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); -+int kex_create_evp_dh(EVP_PKEY **, const BIGNUM *, const BIGNUM *, -+ const BIGNUM *, const BIGNUM *, const BIGNUM *); - - #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) - void dump_digest(const char *, const u_char *, int); diff --git a/openssh-9.0p1-evp-fips-ecdh.patch b/openssh-9.0p1-evp-fips-ecdh.patch deleted file mode 100644 index 0313c6f..0000000 --- a/openssh-9.0p1-evp-fips-ecdh.patch +++ /dev/null @@ -1,207 +0,0 @@ -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac ../openssh-8.7p1/kexecdh.c ./kexecdh.c ---- ../openssh-8.7p1/kexecdh.c 2021-08-20 06:03:49.000000000 +0200 -+++ ./kexecdh.c 2023-04-13 14:30:14.882449593 +0200 -@@ -35,17 +35,57 @@ - #include - - #include -+#include -+#include -+#include -+#include - - #include "sshkey.h" - #include "kex.h" - #include "sshbuf.h" - #include "digest.h" - #include "ssherr.h" -+#include "log.h" - - static int - kex_ecdh_dec_key_group(struct kex *, const struct sshbuf *, EC_KEY *key, - const EC_GROUP *, struct sshbuf **); - -+static EC_KEY * -+generate_ec_keys(int ec_nid) -+{ -+ EC_KEY *client_key = NULL; -+ EVP_PKEY *pkey = NULL; -+ EVP_PKEY_CTX *ctx = NULL; -+ OSSL_PARAM_BLD *param_bld = NULL; -+ OSSL_PARAM *params = NULL; -+ const char *group_name; -+ -+ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL || -+ (param_bld = OSSL_PARAM_BLD_new()) == NULL) -+ goto out; -+ if ((group_name = OSSL_EC_curve_nid2name(ec_nid)) == NULL || -+ OSSL_PARAM_BLD_push_utf8_string(param_bld, -+ OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) != 1 || -+ (params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { -+ error_f("Could not create OSSL_PARAM"); -+ goto out; -+ } -+ if (EVP_PKEY_keygen_init(ctx) != 1 || -+ EVP_PKEY_CTX_set_params(ctx, params) != 1 || -+ EVP_PKEY_generate(ctx, &pkey) != 1 || -+ (client_key = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) { -+ error_f("Could not generate ec keys"); -+ goto out; -+ } -+out: -+ EVP_PKEY_free(pkey); -+ EVP_PKEY_CTX_free(ctx); -+ OSSL_PARAM_BLD_free(param_bld); -+ OSSL_PARAM_free(params); -+ return client_key; -+} -+ - int - kex_ecdh_keypair(struct kex *kex) - { -@@ -55,11 +95,7 @@ - struct sshbuf *buf = NULL; - int r; - -- if ((client_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) { -- r = SSH_ERR_ALLOC_FAIL; -- goto out; -- } -- if (EC_KEY_generate_key(client_key) != 1) { -+ if ((client_key = generate_ec_keys(kex->ec_nid)) == NULL) { - r = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } -@@ -101,11 +137,7 @@ - *server_blobp = NULL; - *shared_secretp = NULL; - -- if ((server_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) { -- r = SSH_ERR_ALLOC_FAIL; -- goto out; -- } -- if (EC_KEY_generate_key(server_key) != 1) { -+ if ((server_key = generate_ec_keys(kex->ec_nid)) == NULL) { - r = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } -@@ -140,11 +172,21 @@ - { - struct sshbuf *buf = NULL; - BIGNUM *shared_secret = NULL; -- EC_POINT *dh_pub = NULL; -- u_char *kbuf = NULL; -- size_t klen = 0; -+ EVP_PKEY_CTX *ctx = NULL; -+ EVP_PKEY *pkey = NULL, *dh_pkey = NULL; -+ OSSL_PARAM_BLD *param_bld = NULL; -+ OSSL_PARAM *params = NULL; -+ u_char *kbuf = NULL, *pub = NULL; -+ size_t klen = 0, publen; -+ const char *group_name; - int r; - -+ /* import EC_KEY to EVP_PKEY */ -+ if ((r = ssh_create_evp_ec(key, kex->ec_nid, &pkey)) != 0) { -+ error_f("Could not create EVP_PKEY"); -+ goto out; -+ } -+ - *shared_secretp = NULL; - - if ((buf = sshbuf_new()) == NULL) { -@@ -153,45 +195,82 @@ - } - if ((r = sshbuf_put_stringb(buf, ec_blob)) != 0) - goto out; -- if ((dh_pub = EC_POINT_new(group)) == NULL) { -+ -+ /* the public key is in the buffer in octet string UNCOMPRESSED -+ * format. See sshbuf_put_ec */ -+ if ((r = sshbuf_get_string(buf, &pub, &publen)) != 0) -+ goto out; -+ sshbuf_reset(buf); -+ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL || -+ (param_bld = OSSL_PARAM_BLD_new()) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } -- if ((r = sshbuf_get_ec(buf, dh_pub, group)) != 0) { -+ if ((group_name = OSSL_EC_curve_nid2name(kex->ec_nid)) == NULL) { -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (OSSL_PARAM_BLD_push_octet_string(param_bld, -+ OSSL_PKEY_PARAM_PUB_KEY, pub, publen) != 1 || -+ OSSL_PARAM_BLD_push_utf8_string(param_bld, -+ OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) != 1 || -+ (params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { -+ error_f("Failed to set params for dh_pkey"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (EVP_PKEY_fromdata_init(ctx) != 1 || -+ EVP_PKEY_fromdata(ctx, &dh_pkey, -+ EVP_PKEY_PUBLIC_KEY, params) != 1 || -+ EVP_PKEY_public_check(ctx) != 1) { -+ error_f("Peer public key import failed"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } -- sshbuf_reset(buf); - - #ifdef DEBUG_KEXECDH - fputs("public key:\n", stderr); -- sshkey_dump_ec_point(group, dh_pub); -+ EVP_PKEY_print_public_fp(stderr, dh_pkey, 0, NULL); - #endif -- if (sshkey_ec_validate_public(group, dh_pub) != 0) { -- r = SSH_ERR_MESSAGE_INCOMPLETE; -+ EVP_PKEY_CTX_free(ctx); -+ ctx = NULL; -+ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL || -+ EVP_PKEY_derive_init(ctx) != 1 || -+ EVP_PKEY_derive_set_peer(ctx, dh_pkey) != 1 || -+ EVP_PKEY_derive(ctx, NULL, &klen) != 1) { -+ error_f("Failed to get derive information"); -+ r = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } -- klen = (EC_GROUP_get_degree(group) + 7) / 8; -- if ((kbuf = malloc(klen)) == NULL || -- (shared_secret = BN_new()) == NULL) { -+ if ((kbuf = malloc(klen)) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } -- if (ECDH_compute_key(kbuf, klen, dh_pub, key, NULL) != (int)klen || -- BN_bin2bn(kbuf, klen, shared_secret) == NULL) { -+ if (EVP_PKEY_derive(ctx, kbuf, &klen) != 1) { - r = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - #ifdef DEBUG_KEXECDH - dump_digest("shared secret", kbuf, klen); - #endif -+ if ((shared_secret = BN_new()) == NULL || -+ (BN_bin2bn(kbuf, klen, shared_secret) == NULL)) { -+ r = SSH_ERR_ALLOC_FAIL; -+ goto out; -+ } - if ((r = sshbuf_put_bignum2(buf, shared_secret)) != 0) - goto out; - *shared_secretp = buf; - buf = NULL; - out: -- EC_POINT_clear_free(dh_pub); -+ EVP_PKEY_CTX_free(ctx); -+ EVP_PKEY_free(pkey); -+ EVP_PKEY_free(dh_pkey); -+ OSSL_PARAM_BLD_free(param_bld); -+ OSSL_PARAM_free(params); - BN_clear_free(shared_secret); - freezero(kbuf, klen); -+ freezero(pub, publen); - sshbuf_free(buf); - return r; - } diff --git a/openssh-9.0p1-evp-fips-kex.patch b/openssh-9.0p1-evp-fips-kex.patch new file mode 100644 index 0000000..36fd1cf --- /dev/null +++ b/openssh-9.0p1-evp-fips-kex.patch @@ -0,0 +1,595 @@ +diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/dh.c openssh-9.0p1-patched/dh.c +--- openssh-9.0p1/dh.c 2023-05-25 09:24:28.730868316 +0200 ++++ openssh-9.0p1-patched/dh.c 2023-05-25 09:23:44.841379532 +0200 +@@ -37,6 +37,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "dh.h" + #include "pathnames.h" +@@ -290,10 +293,15 @@ + int + dh_gen_key(DH *dh, int need) + { +- int pbits; +- const BIGNUM *dh_p, *pub_key; ++ const BIGNUM *dh_p, *dh_g; ++ BIGNUM *pub_key = NULL, *priv_key = NULL; ++ EVP_PKEY *pkey = NULL; ++ EVP_PKEY_CTX *ctx = NULL; ++ OSSL_PARAM_BLD *param_bld = NULL; ++ OSSL_PARAM *params = NULL; ++ int pbits, r = 0; + +- DH_get0_pqg(dh, &dh_p, NULL, NULL); ++ DH_get0_pqg(dh, &dh_p, NULL, &dh_g); + + if (need < 0 || dh_p == NULL || + (pbits = BN_num_bits(dh_p)) <= 0 || +@@ -301,19 +309,85 @@ + return SSH_ERR_INVALID_ARGUMENT; + if (need < 256) + need = 256; ++ ++ if ((param_bld = OSSL_PARAM_BLD_new()) == NULL || ++ (ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL)) == NULL) { ++ OSSL_PARAM_BLD_free(param_bld); ++ return SSH_ERR_ALLOC_FAIL; ++ } ++ ++ if (OSSL_PARAM_BLD_push_BN(param_bld, ++ OSSL_PKEY_PARAM_FFC_P, dh_p) != 1 || ++ OSSL_PARAM_BLD_push_BN(param_bld, ++ OSSL_PKEY_PARAM_FFC_G, dh_g) != 1) { ++ error_f("Could not set p,q,g parameters"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } + /* + * Pollard Rho, Big step/Little Step attacks are O(sqrt(n)), + * so double requested need here. + */ +- if (!DH_set_length(dh, MINIMUM(need * 2, pbits - 1))) +- return SSH_ERR_LIBCRYPTO_ERROR; +- +- if (DH_generate_key(dh) == 0) +- return SSH_ERR_LIBCRYPTO_ERROR; +- DH_get0_key(dh, &pub_key, NULL); +- if (!dh_pub_is_valid(dh, pub_key)) +- return SSH_ERR_INVALID_FORMAT; +- return 0; ++ if (OSSL_PARAM_BLD_push_int(param_bld, ++ OSSL_PKEY_PARAM_DH_PRIV_LEN, ++ MINIMUM(need * 2, pbits - 1)) != 1 || ++ (params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (EVP_PKEY_fromdata_init(ctx) != 1) { ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (EVP_PKEY_fromdata(ctx, &pkey, ++ EVP_PKEY_KEY_PARAMETERS, params) != 1) { ++ error_f("Failed key generation"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ ++ /* reuse context for key generation */ ++ EVP_PKEY_CTX_free(ctx); ++ ctx = NULL; ++ ++ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL || ++ EVP_PKEY_keygen_init(ctx) != 1) { ++ error_f("Could not create or init context"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (EVP_PKEY_generate(ctx, &pkey) != 1) { ++ error_f("Could not generate keys"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (EVP_PKEY_public_check(ctx) != 1) { ++ error_f("The public key is incorrect"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ ++ if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, ++ &pub_key) != 1 || ++ EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, ++ &priv_key) != 1 || ++ DH_set0_key(dh, pub_key, priv_key) != 1) { ++ error_f("Could not set pub/priv keys to DH struct"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ ++ /* transferred */ ++ pub_key = NULL; ++ priv_key = NULL; ++out: ++ OSSL_PARAM_free(params); ++ OSSL_PARAM_BLD_free(param_bld); ++ EVP_PKEY_CTX_free(ctx); ++ EVP_PKEY_free(pkey); ++ BN_clear_free(pub_key); ++ BN_clear_free(priv_key); ++ return r; + } + + DH * +diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/kex.c openssh-9.0p1-patched/kex.c +--- openssh-9.0p1/kex.c 2023-05-25 09:24:28.731868327 +0200 ++++ openssh-9.0p1-patched/kex.c 2023-05-25 09:23:44.841379532 +0200 +@@ -1623,3 +1623,142 @@ + return r; + } + ++#ifdef WITH_OPENSSL ++/* ++ * Creates an EVP_PKEY from the given parameters and keys. ++ * The private key can be omitted. ++ */ ++EVP_PKEY * ++sshkey_create_evp(OSSL_PARAM_BLD *param_bld, EVP_PKEY_CTX *ctx) ++{ ++ EVP_PKEY *ret = NULL; ++ OSSL_PARAM *params = NULL; ++ if (param_bld == NULL || ctx == NULL) { ++ debug2_f("param_bld or ctx is NULL"); ++ return NULL; ++ } ++ if ((params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { ++ debug2_f("Could not build param list"); ++ return NULL; ++ } ++ if (EVP_PKEY_fromdata_init(ctx) != 1 || ++ EVP_PKEY_fromdata(ctx, &ret, EVP_PKEY_KEYPAIR, params) != 1) { ++ debug2_f("EVP_PKEY_fromdata failed"); ++ OSSL_PARAM_free(params); ++ return NULL; ++ } ++ return ret; ++} ++ ++int ++kex_create_evp_ec(EC_KEY *k, int ecdsa_nid, EVP_PKEY **pkey) ++{ ++ OSSL_PARAM_BLD *param_bld = NULL; ++ EVP_PKEY_CTX *ctx = NULL; ++ BN_CTX *bn_ctx = NULL; ++ uint8_t *pub_ser = NULL; ++ const char *group_name; ++ const EC_POINT *pub = NULL; ++ const BIGNUM *priv = NULL; ++ int ret = 0; ++ ++ if (k == NULL) ++ return SSH_ERR_INVALID_ARGUMENT; ++ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL || ++ (param_bld = OSSL_PARAM_BLD_new()) == NULL || ++ (bn_ctx = BN_CTX_new()) == NULL) { ++ ret = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ if ((group_name = OSSL_EC_curve_nid2name(ecdsa_nid)) == NULL || ++ OSSL_PARAM_BLD_push_utf8_string(param_bld, ++ OSSL_PKEY_PARAM_GROUP_NAME, ++ group_name, ++ strlen(group_name)) != 1) { ++ ret = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if ((pub = EC_KEY_get0_public_key(k)) != NULL) { ++ const EC_GROUP *group; ++ size_t len; ++ ++ group = EC_KEY_get0_group(k); ++ len = EC_POINT_point2oct(group, pub, ++ POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); ++ if ((pub_ser = malloc(len)) == NULL) { ++ ret = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ EC_POINT_point2oct(group, ++ pub, ++ POINT_CONVERSION_UNCOMPRESSED, ++ pub_ser, ++ len, ++ bn_ctx); ++ if (OSSL_PARAM_BLD_push_octet_string(param_bld, ++ OSSL_PKEY_PARAM_PUB_KEY, ++ pub_ser, ++ len) != 1) { ++ ret = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ } ++ if ((priv = EC_KEY_get0_private_key(k)) != NULL && ++ OSSL_PARAM_BLD_push_BN(param_bld, ++ OSSL_PKEY_PARAM_PRIV_KEY, priv) != 1) { ++ ret = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if ((*pkey = sshkey_create_evp(param_bld, ctx)) == NULL) { ++ ret = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ ++out: ++ OSSL_PARAM_BLD_free(param_bld); ++ EVP_PKEY_CTX_free(ctx); ++ BN_CTX_free(bn_ctx); ++ free(pub_ser); ++ return ret; ++} ++ ++int ++kex_create_evp_dh(EVP_PKEY **pkey, const BIGNUM *p, const BIGNUM *q, ++ const BIGNUM *g, const BIGNUM *pub, const BIGNUM *priv) ++{ ++ OSSL_PARAM_BLD *param_bld = NULL; ++ EVP_PKEY_CTX *ctx = NULL; ++ int r = 0; ++ ++ /* create EVP_PKEY-DH key */ ++ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL)) == NULL || ++ (param_bld = OSSL_PARAM_BLD_new()) == NULL) { ++ error_f("EVP_PKEY_CTX or PARAM_BLD init failed"); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ if (OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, p) != 1 || ++ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_Q, q) != 1 || ++ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, g) != 1 || ++ OSSL_PARAM_BLD_push_BN(param_bld, ++ OSSL_PKEY_PARAM_PUB_KEY, pub) != 1) { ++ error_f("Failed pushing params to OSSL_PARAM_BLD"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (priv != NULL && ++ OSSL_PARAM_BLD_push_BN(param_bld, ++ OSSL_PKEY_PARAM_PRIV_KEY, priv) != 1) { ++ error_f("Failed pushing private key to OSSL_PARAM_BLD"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if ((*pkey = sshkey_create_evp(param_bld, ctx)) == NULL) ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++out: ++ OSSL_PARAM_BLD_free(param_bld); ++ EVP_PKEY_CTX_free(ctx); ++ return r; ++} ++#endif /* WITH_OPENSSL */ +diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/kexdh.c openssh-9.0p1-patched/kexdh.c +--- openssh-9.0p1/kexdh.c 2023-05-25 09:24:28.674867692 +0200 ++++ openssh-9.0p1-patched/kexdh.c 2023-05-25 09:25:28.494533889 +0200 +@@ -35,6 +35,10 @@ + + #include "openbsd-compat/openssl-compat.h" + #include ++#include ++#include ++#include ++#include + + #include "sshkey.h" + #include "kex.h" +@@ -83,9 +87,12 @@ + kex_dh_compute_key(struct kex *kex, BIGNUM *dh_pub, struct sshbuf *out) + { + BIGNUM *shared_secret = NULL; ++ const BIGNUM *pub, *priv, *p, *q, *g; ++ EVP_PKEY *pkey = NULL, *dh_pkey = NULL; ++ EVP_PKEY_CTX *ctx = NULL; + u_char *kbuf = NULL; + size_t klen = 0; +- int kout, r; ++ int r = 0; + + #ifdef DEBUG_KEXDH + fprintf(stderr, "dh_pub= "); +@@ -100,24 +107,59 @@ + r = SSH_ERR_MESSAGE_INCOMPLETE; + goto out; + } +- klen = DH_size(kex->dh); ++ ++ DH_get0_key(kex->dh, &pub, &priv); ++ DH_get0_pqg(kex->dh, &p, &q, &g); ++ /* import key */ ++ r = kex_create_evp_dh(&pkey, p, q, g, pub, priv); ++ if (r != 0) { ++ error_f("Could not create EVP_PKEY for dh"); ++ ERR_print_errors_fp(stderr); ++ goto out; ++ } ++ /* import peer key ++ * the parameters should be the same as with pkey ++ */ ++ r = kex_create_evp_dh(&dh_pkey, p, q, g, dh_pub, NULL); ++ if (r != 0) { ++ error_f("Could not import peer key for dh"); ++ ERR_print_errors_fp(stderr); ++ goto out; ++ } ++ ++ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL) { ++ error_f("Could not init EVP_PKEY_CTX for dh"); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ if (EVP_PKEY_derive_init(ctx) != 1 || ++ EVP_PKEY_derive_set_peer(ctx, dh_pkey) != 1 || ++ EVP_PKEY_derive(ctx, NULL, &klen) != 1) { ++ error_f("Could not get key size"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } + if ((kbuf = malloc(klen)) == NULL || + (shared_secret = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } +- if ((kout = DH_compute_key(kbuf, dh_pub, kex->dh)) < 0 || +- BN_bin2bn(kbuf, kout, shared_secret) == NULL) { ++ if (EVP_PKEY_derive(ctx, kbuf, &klen) != 1 || ++ BN_bin2bn(kbuf, klen, shared_secret) == NULL) { ++ error_f("Could not derive key"); + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } + #ifdef DEBUG_KEXDH +- dump_digest("shared secret", kbuf, kout); ++ dump_digest("shared secret", kbuf, klen); + #endif + r = sshbuf_put_bignum2(out, shared_secret); + out: + freezero(kbuf, klen); + BN_clear_free(shared_secret); ++ EVP_PKEY_free(pkey); ++ EVP_PKEY_free(dh_pkey); ++ EVP_PKEY_CTX_free(ctx); + return r; + } + +diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.0p1/kex.h openssh-9.0p1-patched/kex.h +--- openssh-9.0p1/kex.h 2023-05-25 09:24:28.725868260 +0200 ++++ openssh-9.0p1-patched/kex.h 2023-05-25 09:23:44.841379532 +0200 +@@ -33,6 +33,9 @@ + # include + # include + # include ++# include ++# include ++# include + # ifdef OPENSSL_HAS_ECC + # include + # else /* OPENSSL_HAS_ECC */ +@@ -283,6 +286,9@@ + const u_char pub[CURVE25519_SIZE], struct sshbuf *out, int) + __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) + __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); ++int kex_create_evp_dh(EVP_PKEY **, const BIGNUM *, const BIGNUM *, ++ const BIGNUM *, const BIGNUM *, const BIGNUM *); ++int kex_create_evp_ec(EC_KEY *k, int ecdsa_nid, EVP_PKEY **pkey); + + #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) + void dump_digest(const char *, const u_char *, int); +diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac ../openssh-8.7p1/kexecdh.c ./kexecdh.c +--- ../openssh-8.7p1/kexecdh.c 2021-08-20 06:03:49.000000000 +0200 ++++ ./kexecdh.c 2023-04-13 14:30:14.882449593 +0200 +@@ -35,17 +35,57 @@ + #include + + #include ++#include ++#include ++#include ++#include + + #include "sshkey.h" + #include "kex.h" + #include "sshbuf.h" + #include "digest.h" + #include "ssherr.h" ++#include "log.h" + + static int + kex_ecdh_dec_key_group(struct kex *, const struct sshbuf *, EC_KEY *key, + const EC_GROUP *, struct sshbuf **); + ++static EC_KEY * ++generate_ec_keys(int ec_nid) ++{ ++ EC_KEY *client_key = NULL; ++ EVP_PKEY *pkey = NULL; ++ EVP_PKEY_CTX *ctx = NULL; ++ OSSL_PARAM_BLD *param_bld = NULL; ++ OSSL_PARAM *params = NULL; ++ const char *group_name; ++ ++ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL || ++ (param_bld = OSSL_PARAM_BLD_new()) == NULL) ++ goto out; ++ if ((group_name = OSSL_EC_curve_nid2name(ec_nid)) == NULL || ++ OSSL_PARAM_BLD_push_utf8_string(param_bld, ++ OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) != 1 || ++ (params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { ++ error_f("Could not create OSSL_PARAM"); ++ goto out; ++ } ++ if (EVP_PKEY_keygen_init(ctx) != 1 || ++ EVP_PKEY_CTX_set_params(ctx, params) != 1 || ++ EVP_PKEY_generate(ctx, &pkey) != 1 || ++ (client_key = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) { ++ error_f("Could not generate ec keys"); ++ goto out; ++ } ++out: ++ EVP_PKEY_free(pkey); ++ EVP_PKEY_CTX_free(ctx); ++ OSSL_PARAM_BLD_free(param_bld); ++ OSSL_PARAM_free(params); ++ return client_key; ++} ++ + int + kex_ecdh_keypair(struct kex *kex) + { +@@ -55,11 +95,7 @@ + struct sshbuf *buf = NULL; + int r; + +- if ((client_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) { +- r = SSH_ERR_ALLOC_FAIL; +- goto out; +- } +- if (EC_KEY_generate_key(client_key) != 1) { ++ if ((client_key = generate_ec_keys(kex->ec_nid)) == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } +@@ -101,11 +137,7 @@ + *server_blobp = NULL; + *shared_secretp = NULL; + +- if ((server_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) { +- r = SSH_ERR_ALLOC_FAIL; +- goto out; +- } +- if (EC_KEY_generate_key(server_key) != 1) { ++ if ((server_key = generate_ec_keys(kex->ec_nid)) == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } +@@ -140,11 +172,21 @@ + { + struct sshbuf *buf = NULL; + BIGNUM *shared_secret = NULL; +- EC_POINT *dh_pub = NULL; +- u_char *kbuf = NULL; +- size_t klen = 0; ++ EVP_PKEY_CTX *ctx = NULL; ++ EVP_PKEY *pkey = NULL, *dh_pkey = NULL; ++ OSSL_PARAM_BLD *param_bld = NULL; ++ OSSL_PARAM *params = NULL; ++ u_char *kbuf = NULL, *pub = NULL; ++ size_t klen = 0, publen; ++ const char *group_name; + int r; + ++ /* import EC_KEY to EVP_PKEY */ ++ if ((r = kex_create_evp_ec(key, kex->ec_nid, &pkey)) != 0) { ++ error_f("Could not create EVP_PKEY"); ++ goto out; ++ } ++ + *shared_secretp = NULL; + + if ((buf = sshbuf_new()) == NULL) { +@@ -153,45 +195,82 @@ + } + if ((r = sshbuf_put_stringb(buf, ec_blob)) != 0) + goto out; +- if ((dh_pub = EC_POINT_new(group)) == NULL) { ++ ++ /* the public key is in the buffer in octet string UNCOMPRESSED ++ * format. See sshbuf_put_ec */ ++ if ((r = sshbuf_get_string(buf, &pub, &publen)) != 0) ++ goto out; ++ sshbuf_reset(buf); ++ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL || ++ (param_bld = OSSL_PARAM_BLD_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } +- if ((r = sshbuf_get_ec(buf, dh_pub, group)) != 0) { ++ if ((group_name = OSSL_EC_curve_nid2name(kex->ec_nid)) == NULL) { ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (OSSL_PARAM_BLD_push_octet_string(param_bld, ++ OSSL_PKEY_PARAM_PUB_KEY, pub, publen) != 1 || ++ OSSL_PARAM_BLD_push_utf8_string(param_bld, ++ OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) != 1 || ++ (params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { ++ error_f("Failed to set params for dh_pkey"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; ++ goto out; ++ } ++ if (EVP_PKEY_fromdata_init(ctx) != 1 || ++ EVP_PKEY_fromdata(ctx, &dh_pkey, ++ EVP_PKEY_PUBLIC_KEY, params) != 1 || ++ EVP_PKEY_public_check(ctx) != 1) { ++ error_f("Peer public key import failed"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } +- sshbuf_reset(buf); + + #ifdef DEBUG_KEXECDH + fputs("public key:\n", stderr); +- sshkey_dump_ec_point(group, dh_pub); ++ EVP_PKEY_print_public_fp(stderr, dh_pkey, 0, NULL); + #endif +- if (sshkey_ec_validate_public(group, dh_pub) != 0) { +- r = SSH_ERR_MESSAGE_INCOMPLETE; ++ EVP_PKEY_CTX_free(ctx); ++ ctx = NULL; ++ if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL || ++ EVP_PKEY_derive_init(ctx) != 1 || ++ EVP_PKEY_derive_set_peer(ctx, dh_pkey) != 1 || ++ EVP_PKEY_derive(ctx, NULL, &klen) != 1) { ++ error_f("Failed to get derive information"); ++ r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } +- klen = (EC_GROUP_get_degree(group) + 7) / 8; +- if ((kbuf = malloc(klen)) == NULL || +- (shared_secret = BN_new()) == NULL) { ++ if ((kbuf = malloc(klen)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } +- if (ECDH_compute_key(kbuf, klen, dh_pub, key, NULL) != (int)klen || +- BN_bin2bn(kbuf, klen, shared_secret) == NULL) { ++ if (EVP_PKEY_derive(ctx, kbuf, &klen) != 1) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } + #ifdef DEBUG_KEXECDH + dump_digest("shared secret", kbuf, klen); + #endif ++ if ((shared_secret = BN_new()) == NULL || ++ (BN_bin2bn(kbuf, klen, shared_secret) == NULL)) { ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } + if ((r = sshbuf_put_bignum2(buf, shared_secret)) != 0) + goto out; + *shared_secretp = buf; + buf = NULL; + out: +- EC_POINT_clear_free(dh_pub); ++ EVP_PKEY_CTX_free(ctx); ++ EVP_PKEY_free(pkey); ++ EVP_PKEY_free(dh_pkey); ++ OSSL_PARAM_BLD_free(param_bld); ++ OSSL_PARAM_free(params); + BN_clear_free(shared_secret); + freezero(kbuf, klen); ++ freezero(pub, publen); + sshbuf_free(buf); + return r; + } diff --git a/openssh-9.3p1-merged-openssl-evp.patch b/openssh-9.3p1-merged-openssl-evp.patch deleted file mode 100644 index 6a30485..0000000 --- a/openssh-9.3p1-merged-openssl-evp.patch +++ /dev/null @@ -1,1237 +0,0 @@ -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.3p1/digest.h openssh-9.3p1-patched/digest.h ---- openssh-9.3p1/digest.h 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1-patched/digest.h 2023-06-06 15:52:25.602551466 +0200 -@@ -32,6 +32,12 @@ - struct sshbuf; - struct ssh_digest_ctx; - -+#ifdef WITH_OPENSSL -+#include -+/* Converts internal digest representation to the OpenSSL one */ -+const EVP_MD *ssh_digest_to_md(int digest_type); -+#endif -+ - /* Looks up a digest algorithm by name */ - int ssh_digest_alg_by_name(const char *name); - -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.3p1/digest-openssl.c openssh-9.3p1-patched/digest-openssl.c ---- openssh-9.3p1/digest-openssl.c 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1-patched/digest-openssl.c 2023-06-06 15:52:25.601551454 +0200 -@@ -64,6 +64,22 @@ - { -1, NULL, 0, NULL }, - }; - -+const EVP_MD * -+ssh_digest_to_md(int digest_type) -+{ -+ switch (digest_type) { -+ case SSH_DIGEST_SHA1: -+ return EVP_sha1(); -+ case SSH_DIGEST_SHA256: -+ return EVP_sha256(); -+ case SSH_DIGEST_SHA384: -+ return EVP_sha384(); -+ case SSH_DIGEST_SHA512: -+ return EVP_sha512(); -+ } -+ return NULL; -+} -+ - static const struct ssh_digest * - ssh_digest_by_alg(int alg) - { -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.3p1/ssh-dss.c openssh-9.3p1-patched/ssh-dss.c ---- openssh-9.3p1/ssh-dss.c 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1-patched/ssh-dss.c 2023-06-06 15:52:25.624551743 +0200 -@@ -32,6 +32,8 @@ - #include - #include - #include -+#include -+#include - - #include - #include -@@ -261,11 +263,15 @@ - const u_char *data, size_t datalen, - const char *alg, const char *sk_provider, const char *sk_pin, u_int compat) - { -+ EVP_PKEY *pkey = NULL; - DSA_SIG *sig = NULL; - const BIGNUM *sig_r, *sig_s; -- u_char digest[SSH_DIGEST_MAX_LENGTH], sigblob[SIGBLOB_LEN]; -- size_t rlen, slen, len, dlen = ssh_digest_bytes(SSH_DIGEST_SHA1); -+ u_char sigblob[SIGBLOB_LEN]; -+ size_t rlen, slen; -+ int len; - struct sshbuf *b = NULL; -+ u_char *sigb = NULL; -+ const u_char *psig = NULL; - int ret = SSH_ERR_INVALID_ARGUMENT; - - if (lenp != NULL) -@@ -276,17 +282,23 @@ - if (key == NULL || key->dsa == NULL || - sshkey_type_plain(key->type) != KEY_DSA) - return SSH_ERR_INVALID_ARGUMENT; -- if (dlen == 0) -- return SSH_ERR_INTERNAL_ERROR; - -- if ((ret = ssh_digest_memory(SSH_DIGEST_SHA1, data, datalen, -- digest, sizeof(digest))) != 0) -+ if ((ret = ssh_create_evp_dss(key, &pkey)) != 0) -+ return ret; -+ ret = sshkey_calculate_signature(pkey, SSH_DIGEST_SHA1, &sigb, &len, -+ data, datalen); -+ EVP_PKEY_free(pkey); -+ if (ret < 0) { - goto out; -+ } - -- if ((sig = DSA_do_sign(digest, dlen, key->dsa)) == NULL) { -+ psig = sigb; -+ if ((sig = d2i_DSA_SIG(NULL, &psig, len)) == NULL) { - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } -+ free(sigb); -+ sigb = NULL; - - DSA_SIG_get0(sig, &sig_r, &sig_s); - rlen = BN_num_bytes(sig_r); -@@ -319,7 +331,7 @@ - *lenp = len; - ret = 0; - out: -- explicit_bzero(digest, sizeof(digest)); -+ free(sigb); - DSA_SIG_free(sig); - sshbuf_free(b); - return ret; -@@ -331,20 +343,20 @@ - const u_char *data, size_t dlen, const char *alg, u_int compat, - struct sshkey_sig_details **detailsp) - { -+ EVP_PKEY *pkey = NULL; - DSA_SIG *dsig = NULL; - BIGNUM *sig_r = NULL, *sig_s = NULL; -- u_char digest[SSH_DIGEST_MAX_LENGTH], *sigblob = NULL; -- size_t len, hlen = ssh_digest_bytes(SSH_DIGEST_SHA1); -+ u_char *sigblob = NULL; -+ size_t len, slen; - int ret = SSH_ERR_INTERNAL_ERROR; - struct sshbuf *b = NULL; - char *ktype = NULL; -+ u_char *sigb = NULL, *psig = NULL; - - if (key == NULL || key->dsa == NULL || - sshkey_type_plain(key->type) != KEY_DSA || - sig == NULL || siglen == 0) - return SSH_ERR_INVALID_ARGUMENT; -- if (hlen == 0) -- return SSH_ERR_INTERNAL_ERROR; - - /* fetch signature */ - if ((b = sshbuf_from(sig, siglen)) == NULL) -@@ -386,25 +398,28 @@ - } - sig_r = sig_s = NULL; /* transferred */ - -- /* sha1 the data */ -- if ((ret = ssh_digest_memory(SSH_DIGEST_SHA1, data, dlen, -- digest, sizeof(digest))) != 0) -+ if ((slen = i2d_DSA_SIG(dsig, NULL)) == 0) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; -- -- switch (DSA_do_verify(digest, hlen, dsig, key->dsa)) { -- case 1: -- ret = 0; -- break; -- case 0: -- ret = SSH_ERR_SIGNATURE_INVALID; -+ } -+ if ((sigb = malloc(slen)) == NULL) { -+ ret = SSH_ERR_ALLOC_FAIL; - goto out; -- default: -+ } -+ psig = sigb; -+ if ((slen = i2d_DSA_SIG(dsig, &psig)) == 0) { - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - -+ if ((ret = ssh_create_evp_dss(key, &pkey)) != 0) -+ goto out; -+ ret = sshkey_verify_signature(pkey, SSH_DIGEST_SHA1, data, dlen, -+ sigb, slen); -+ EVP_PKEY_free(pkey); -+ - out: -- explicit_bzero(digest, sizeof(digest)); -+ free(sigb); - DSA_SIG_free(dsig); - BN_clear_free(sig_r); - BN_clear_free(sig_s); -@@ -415,6 +430,65 @@ - return ret; - } - -+int -+ssh_create_evp_dss(const struct sshkey *k, EVP_PKEY **pkey) -+{ -+ OSSL_PARAM_BLD *param_bld = NULL; -+ EVP_PKEY_CTX *ctx = NULL; -+ const BIGNUM *p = NULL, *q = NULL, *g = NULL, *pub = NULL, *priv = NULL; -+ int ret = 0; -+ -+ if (k == NULL) -+ return SSH_ERR_INVALID_ARGUMENT; -+ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "DSA", NULL)) == NULL || -+ (param_bld = OSSL_PARAM_BLD_new()) == NULL) { -+ ret = SSH_ERR_ALLOC_FAIL; -+ goto out; -+ } -+ -+ DSA_get0_pqg(k->dsa, &p, &q, &g); -+ DSA_get0_key(k->dsa, &pub, &priv); -+ -+ if (p != NULL && -+ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, p) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (q != NULL && -+ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_Q, q) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (g != NULL && -+ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, g) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (pub != NULL && -+ OSSL_PARAM_BLD_push_BN(param_bld, -+ OSSL_PKEY_PARAM_PUB_KEY, -+ pub) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (priv != NULL && -+ OSSL_PARAM_BLD_push_BN(param_bld, -+ OSSL_PKEY_PARAM_PRIV_KEY, -+ priv) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if ((*pkey = sshkey_create_evp(param_bld, ctx)) == NULL) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ -+out: -+ OSSL_PARAM_BLD_free(param_bld); -+ EVP_PKEY_CTX_free(ctx); -+ return ret; -+} -+ - static const struct sshkey_impl_funcs sshkey_dss_funcs = { - /* .size = */ ssh_dss_size, - /* .alloc = */ ssh_dss_alloc, -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.3p1/ssh-ecdsa.c openssh-9.3p1-patched/ssh-ecdsa.c ---- openssh-9.3p1/ssh-ecdsa.c 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1-patched/ssh-ecdsa.c 2023-06-06 15:52:25.626551768 +0200 -@@ -34,6 +34,8 @@ - #include - #include - #include -+#include -+#include - - #include - -@@ -44,6 +44,9 @@ - #include "digest.h" - #define SSHKEY_INTERNAL - #include "sshkey.h" -+#ifdef ENABLE_PKCS11 -+#include "ssh-pkcs11.h" -+#endif - - #include "openbsd-compat/openssl-compat.h" - -@@ -126,19 +128,29 @@ - static int - ssh_ecdsa_generate(struct sshkey *k, int bits) - { -- EC_KEY *private; -+ EVP_PKEY_CTX *ctx = NULL; -+ EVP_PKEY *res = NULL; - - if ((k->ecdsa_nid = sshkey_ecdsa_bits_to_nid(bits)) == -1) - return SSH_ERR_KEY_LENGTH; -- if ((private = EC_KEY_new_by_curve_name(k->ecdsa_nid)) == NULL) -+ -+ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL) - return SSH_ERR_ALLOC_FAIL; -- if (EC_KEY_generate_key(private) != 1) { -- EC_KEY_free(private); -+ -+ if (EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_CTX_set_group_name(ctx, OBJ_nid2sn(k->ecdsa_nid)) <= 0 -+ || EVP_PKEY_keygen(ctx, &res) <= 0) { -+ EVP_PKEY_CTX_free(ctx); -+ EVP_PKEY_free(res); - return SSH_ERR_LIBCRYPTO_ERROR; - } -- EC_KEY_set_asn1_flag(private, OPENSSL_EC_NAMED_CURVE); -- k->ecdsa = private; -- return 0; -+ /* This function is deprecated in OpenSSL 3.0 but OpenSSH doesn't worry about it*/ -+ k->ecdsa = EVP_PKEY_get1_EC_KEY(res); -+ if (k->ecdsa) -+ EC_KEY_set_asn1_flag(k->ecdsa, OPENSSL_EC_NAMED_CURVE); -+ -+ EVP_PKEY_CTX_free(ctx); -+ EVP_PKEY_free(res); -+ return (k->ecdsa) ? 0 : SSH_ERR_LIBCRYPTO_ERROR; - } - - static int -@@ -228,11 +240,13 @@ - const u_char *data, size_t dlen, - const char *alg, const char *sk_provider, const char *sk_pin, u_int compat) - { -+ EVP_PKEY *pkey = NULL; - ECDSA_SIG *esig = NULL; -+ unsigned char *sigb = NULL; -+ const unsigned char *psig; - const BIGNUM *sig_r, *sig_s; - int hash_alg; -- u_char digest[SSH_DIGEST_MAX_LENGTH]; -- size_t len, hlen; -+ int len; - struct sshbuf *b = NULL, *bb = NULL; - int ret = SSH_ERR_INTERNAL_ERROR; - -@@ -245,18 +259,33 @@ - sshkey_type_plain(key->type) != KEY_ECDSA) - return SSH_ERR_INVALID_ARGUMENT; - -- if ((hash_alg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1 || -- (hlen = ssh_digest_bytes(hash_alg)) == 0) -+ if ((hash_alg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1) - return SSH_ERR_INTERNAL_ERROR; -- if ((ret = ssh_digest_memory(hash_alg, data, dlen, -- digest, sizeof(digest))) != 0) -+ -+#ifdef ENABLE_PKCS11 -+ if (is_ecdsa_pkcs11(key->ecdsa)) { -+ if ((pkey = EVP_PKEY_new()) == NULL || -+ EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa) != 1) -+ return SSH_ERR_ALLOC_FAIL; -+ } else { -+#endif -+ if ((ret = ssh_create_evp_ec(key->ecdsa, key->ecdsa_nid, &pkey)) != 0) -+ return ret; -+#ifdef ENABLE_PKCS11 -+ } -+#endif -+ ret = sshkey_calculate_signature(pkey, hash_alg, &sigb, &len, data, -+ dlen); -+ EVP_PKEY_free(pkey); -+ if (ret < 0) { - goto out; -+ } - -- if ((esig = ECDSA_do_sign(digest, hlen, key->ecdsa)) == NULL) { -+ psig = sigb; -+ if (d2i_ECDSA_SIG(&esig, &psig, len) == NULL) { - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } -- - if ((bb = sshbuf_new()) == NULL || (b = sshbuf_new()) == NULL) { - ret = SSH_ERR_ALLOC_FAIL; - goto out; -@@ -280,7 +309,7 @@ - *lenp = len; - ret = 0; - out: -- explicit_bzero(digest, sizeof(digest)); -+ free(sigb); - sshbuf_free(b); - sshbuf_free(bb); - ECDSA_SIG_free(esig); -@@ -293,22 +322,21 @@ - const u_char *data, size_t dlen, const char *alg, u_int compat, - struct sshkey_sig_details **detailsp) - { -+ EVP_PKEY *pkey = NULL; - ECDSA_SIG *esig = NULL; - BIGNUM *sig_r = NULL, *sig_s = NULL; -- int hash_alg; -- u_char digest[SSH_DIGEST_MAX_LENGTH]; -- size_t hlen; -+ int hash_alg, len; - int ret = SSH_ERR_INTERNAL_ERROR; - struct sshbuf *b = NULL, *sigbuf = NULL; - char *ktype = NULL; -+ unsigned char *sigb = NULL, *psig = NULL; - - if (key == NULL || key->ecdsa == NULL || - sshkey_type_plain(key->type) != KEY_ECDSA || - sig == NULL || siglen == 0) - return SSH_ERR_INVALID_ARGUMENT; - -- if ((hash_alg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1 || -- (hlen = ssh_digest_bytes(hash_alg)) == 0) -+ if ((hash_alg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1) - return SSH_ERR_INTERNAL_ERROR; - - /* fetch signature */ -@@ -344,28 +372,33 @@ - } - sig_r = sig_s = NULL; /* transferred */ - -- if (sshbuf_len(sigbuf) != 0) { -- ret = SSH_ERR_UNEXPECTED_TRAILING_DATA; -+ /* Figure out the length */ -+ if ((len = i2d_ECDSA_SIG(esig, NULL)) == 0) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } -- if ((ret = ssh_digest_memory(hash_alg, data, dlen, -- digest, sizeof(digest))) != 0) -- goto out; -- -- switch (ECDSA_do_verify(digest, hlen, esig, key->ecdsa)) { -- case 1: -- ret = 0; -- break; -- case 0: -- ret = SSH_ERR_SIGNATURE_INVALID; -+ if ((sigb = malloc(len)) == NULL) { -+ ret = SSH_ERR_ALLOC_FAIL; - goto out; -- default: -+ } -+ psig = sigb; -+ if ((len = i2d_ECDSA_SIG(esig, &psig)) == 0) { - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - -+ if (sshbuf_len(sigbuf) != 0) { -+ ret = SSH_ERR_UNEXPECTED_TRAILING_DATA; -+ goto out; -+ } -+ -+ if (ssh_create_evp_ec(key->ecdsa, key->ecdsa_nid, &pkey) != 0) -+ goto out; -+ ret = sshkey_verify_signature(pkey, hash_alg, data, dlen, sigb, len); -+ EVP_PKEY_free(pkey); -+ - out: -- explicit_bzero(digest, sizeof(digest)); -+ free(sigb); - sshbuf_free(sigbuf); - sshbuf_free(b); - ECDSA_SIG_free(esig); -@@ -375,6 +408,79 @@ - return ret; - } - -+int -+ssh_create_evp_ec(EC_KEY *k, int ecdsa_nid, EVP_PKEY **pkey) -+{ -+ OSSL_PARAM_BLD *param_bld = NULL; -+ EVP_PKEY_CTX *ctx = NULL; -+ BN_CTX *bn_ctx = NULL; -+ uint8_t *pub_ser = NULL; -+ const char *group_name; -+ const EC_POINT *pub = NULL; -+ const BIGNUM *priv = NULL; -+ int ret = 0; -+ -+ if (k == NULL) -+ return SSH_ERR_INVALID_ARGUMENT; -+ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL || -+ (param_bld = OSSL_PARAM_BLD_new()) == NULL || -+ (bn_ctx = BN_CTX_new()) == NULL) { -+ ret = SSH_ERR_ALLOC_FAIL; -+ goto out; -+ } -+ -+ if ((group_name = OSSL_EC_curve_nid2name(ecdsa_nid)) == NULL || -+ OSSL_PARAM_BLD_push_utf8_string(param_bld, -+ OSSL_PKEY_PARAM_GROUP_NAME, -+ group_name, -+ strlen(group_name)) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if ((pub = EC_KEY_get0_public_key(k)) != NULL) { -+ const EC_GROUP *group; -+ size_t len; -+ -+ group = EC_KEY_get0_group(k); -+ len = EC_POINT_point2oct(group, pub, -+ POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); -+ if ((pub_ser = malloc(len)) == NULL) { -+ ret = SSH_ERR_ALLOC_FAIL; -+ goto out; -+ } -+ EC_POINT_point2oct(group, -+ pub, -+ POINT_CONVERSION_UNCOMPRESSED, -+ pub_ser, -+ len, -+ bn_ctx); -+ if (OSSL_PARAM_BLD_push_octet_string(param_bld, -+ OSSL_PKEY_PARAM_PUB_KEY, -+ pub_ser, -+ len) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ } -+ if ((priv = EC_KEY_get0_private_key(k)) != NULL && -+ OSSL_PARAM_BLD_push_BN(param_bld, -+ OSSL_PKEY_PARAM_PRIV_KEY, priv) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if ((*pkey = sshkey_create_evp(param_bld, ctx)) == NULL) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ -+out: -+ OSSL_PARAM_BLD_free(param_bld); -+ EVP_PKEY_CTX_free(ctx); -+ BN_CTX_free(bn_ctx); -+ free(pub_ser); -+ return ret; -+} -+ - /* NB. not static; used by ECDSA-SK */ - const struct sshkey_impl_funcs sshkey_ecdsa_funcs = { - /* .size = */ ssh_ecdsa_size, -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.3p1/sshkey.c openssh-9.3p1-patched/sshkey.c ---- openssh-9.3p1/sshkey.c 2023-06-06 15:53:36.608444190 +0200 -+++ openssh-9.3p1-patched/sshkey.c 2023-06-06 15:52:25.625551756 +0200 -@@ -34,6 +34,8 @@ - #include - #include - #include -+#include -+#include - #endif - - #include "crypto_api.h" -@@ -575,6 +577,86 @@ - } - - #ifdef WITH_OPENSSL -+int -+sshkey_calculate_signature(EVP_PKEY *pkey, int hash_alg, u_char **sigp, -+ int *lenp, const u_char *data, size_t datalen) -+{ -+ EVP_MD_CTX *ctx = NULL; -+ u_char *sig = NULL; -+ int ret, slen; -+ size_t len; -+ -+ if (sigp == NULL || lenp == NULL) { -+ return SSH_ERR_INVALID_ARGUMENT; -+ } -+ -+ slen = EVP_PKEY_get_size(pkey); -+ if (slen <= 0 || slen > SSHBUF_MAX_BIGNUM) -+ return SSH_ERR_INVALID_ARGUMENT; -+ -+ len = slen; -+ if ((sig = malloc(slen)) == NULL) { -+ return SSH_ERR_ALLOC_FAIL; -+ } -+ -+ if ((ctx = EVP_MD_CTX_new()) == NULL) { -+ ret = SSH_ERR_ALLOC_FAIL; -+ goto error; -+ } -+ if (EVP_DigestSignInit(ctx, NULL, ssh_digest_to_md(hash_alg), -+ NULL, pkey) != 1 || -+ EVP_DigestSignUpdate(ctx, data, datalen) != 1 || -+ EVP_DigestSignFinal(ctx, sig, &len) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto error; -+ } -+ -+ *sigp = sig; -+ *lenp = len; -+ /* Now owned by the caller */ -+ sig = NULL; -+ ret = 0; -+ -+error: -+ EVP_MD_CTX_free(ctx); -+ free(sig); -+ return ret; -+} -+ -+int -+sshkey_verify_signature(EVP_PKEY *pkey, int hash_alg, const u_char *data, -+ size_t datalen, u_char *sigbuf, int siglen) -+{ -+ EVP_MD_CTX *ctx = NULL; -+ int ret; -+ -+ if ((ctx = EVP_MD_CTX_new()) == NULL) { -+ return SSH_ERR_ALLOC_FAIL; -+ } -+ if (EVP_DigestVerifyInit(ctx, NULL, ssh_digest_to_md(hash_alg), -+ NULL, pkey) != 1 || -+ EVP_DigestVerifyUpdate(ctx, data, datalen) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto done; -+ } -+ ret = EVP_DigestVerifyFinal(ctx, sigbuf, siglen); -+ switch (ret) { -+ case 1: -+ ret = 0; -+ break; -+ case 0: -+ ret = SSH_ERR_SIGNATURE_INVALID; -+ break; -+ default: -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ break; -+ } -+ -+done: -+ EVP_MD_CTX_free(ctx); -+ return ret; -+} -+ - /* XXX: these are really begging for a table-driven approach */ - int - sshkey_curve_name_to_nid(const char *name) -@@ -3763,3 +3845,27 @@ - return 0; - } - #endif /* WITH_XMSS */ -+ -+#ifdef WITH_OPENSSL -+EVP_PKEY * -+sshkey_create_evp(OSSL_PARAM_BLD *param_bld, EVP_PKEY_CTX *ctx) -+{ -+ EVP_PKEY *ret = NULL; -+ OSSL_PARAM *params = NULL; -+ if (param_bld == NULL || ctx == NULL) { -+ debug2_f("param_bld or ctx is NULL"); -+ return NULL; -+ } -+ if ((params = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) { -+ debug2_f("Could not build param list"); -+ return NULL; -+ } -+ if (EVP_PKEY_fromdata_init(ctx) != 1 || -+ EVP_PKEY_fromdata(ctx, &ret, EVP_PKEY_KEYPAIR, params) != 1) { -+ debug2_f("EVP_PKEY_fromdata failed"); -+ OSSL_PARAM_free(params); -+ return NULL; -+ } -+ return ret; -+} -+#endif /* WITH_OPENSSL */ -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.3p1/sshkey.h openssh-9.3p1-patched/sshkey.h ---- openssh-9.3p1/sshkey.h 2023-06-06 15:53:36.608444190 +0200 -+++ openssh-9.3p1-patched/sshkey.h 2023-06-06 15:52:25.626551768 +0200 -@@ -31,6 +31,9 @@ - #ifdef WITH_OPENSSL - #include - #include -+#include -+#include -+#include - # ifdef OPENSSL_HAS_ECC - # include - # include -@@ -266,6 +266,10 @@ - const char *sshkey_ssh_name_plain(const struct sshkey *); - int sshkey_names_valid2(const char *, int, int); - char *sshkey_alg_list(int, int, int, char); -+int sshkey_calculate_signature(EVP_PKEY*, int, u_char **, -+ int *, const u_char *, size_t); -+int sshkey_verify_signature(EVP_PKEY *, int, const u_char *, -+ size_t, u_char *, int); - - int sshkey_from_blob(const u_char *, size_t, struct sshkey **); - int sshkey_fromb(struct sshbuf *, struct sshkey **); -@@ -324,6 +331,13 @@ - - void sshkey_sig_details_free(struct sshkey_sig_details *); - -+#ifdef WITH_OPENSSL -+EVP_PKEY *sshkey_create_evp(OSSL_PARAM_BLD *, EVP_PKEY_CTX *); -+int ssh_create_evp_dss(const struct sshkey *, EVP_PKEY **); -+int ssh_create_evp_rsa(const struct sshkey *, EVP_PKEY **); -+int ssh_create_evp_ec(EC_KEY *, int, EVP_PKEY **); -+#endif /* WITH_OPENSSL */ -+ - #ifdef SSHKEY_INTERNAL - int sshkey_sk_fields_equal(const struct sshkey *a, const struct sshkey *b); - void sshkey_sk_cleanup(struct sshkey *k); -@@ -338,6 +352,10 @@ - #endif - #endif - -+#ifdef ENABLE_PKCS11 -+int pkcs11_get_ecdsa_idx(void); -+#endif -+ - #if !defined(WITH_OPENSSL) - # undef RSA - # undef DSA -diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c ---- a/ssh-pkcs11.c (revision 8241b9c0529228b4b86d88b1a6076fb9f97e4a99) -+++ b/ssh-pkcs11.c (date 1703110934679) -@@ -620,8 +620,24 @@ - - return (0); - } -+ -+int -+is_ecdsa_pkcs11(EC_KEY *ecdsa) -+{ -+ if (EC_KEY_get_ex_data(ecdsa, ec_key_idx) != NULL) -+ return 1; -+ return 0; -+} - #endif /* OPENSSL_HAS_ECC && HAVE_EC_KEY_METHOD_NEW */ - -+int -+is_rsa_pkcs11(RSA *rsa) -+{ -+ if (RSA_get_ex_data(rsa, rsa_idx) != NULL) -+ return 1; -+ return 0; -+} -+ - /* remove trailing spaces. Note, that this does NOT guarantee the buffer - * will be null terminated if there are no trailing spaces! */ - static char * -diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c ---- a/ssh-pkcs11-client.c (revision 8241b9c0529228b4b86d88b1a6076fb9f97e4a99) -+++ b/ssh-pkcs11-client.c (date 1703110830967) -@@ -402,8 +402,36 @@ - if (helper->nrsa == 0 && helper->nec == 0) - helper_terminate(helper); - } -+ -+int -+is_ecdsa_pkcs11(EC_KEY *ecdsa) -+{ -+ const EC_KEY_METHOD *meth; -+ ECDSA_SIG *(*sign_sig)(const unsigned char *dgst, int dgstlen, -+ const BIGNUM *kinv, const BIGNUM *rp, EC_KEY *eckey) = NULL; -+ -+ meth = EC_KEY_get_method(ecdsa); -+ EC_KEY_METHOD_get_sign(meth, NULL, NULL, &sign_sig); -+ if (sign_sig == ecdsa_do_sign) -+ return 1; -+ return 0; -+} - #endif /* defined(OPENSSL_HAS_ECC) && defined(HAVE_EC_KEY_METHOD_NEW) */ - -+int -+is_rsa_pkcs11(RSA *rsa) -+{ -+ const RSA_METHOD *meth; -+ int (*priv_enc)(int flen, const unsigned char *from, -+ unsigned char *to, RSA *rsa, int padding) = NULL; -+ -+ meth = RSA_get_method(rsa); -+ priv_enc = RSA_meth_get_priv_enc(meth); -+ if (priv_enc == rsa_encrypt) -+ return 1; -+ return 0; -+} -+ - /* redirect private key crypto operations to the ssh-pkcs11-helper */ - static void - wrap_key(struct helper *helper, struct sshkey *k) -diff --git a/ssh-pkcs11.h b/ssh-pkcs11.h ---- a/ssh-pkcs11.h (revision 8241b9c0529228b4b86d88b1a6076fb9f97e4a99) -+++ b/ssh-pkcs11.h (date 1703111023334) -@@ -38,6 +38,12 @@ - /* Only available in ssh-pkcs11-client.c so far */ - int pkcs11_make_cert(const struct sshkey *, - const struct sshkey *, struct sshkey **); -+ -+#ifdef HAVE_EC_KEY_METHOD_NEW -+int is_ecdsa_pkcs11(EC_KEY *ecdsa); -+#endif -+int is_rsa_pkcs11(RSA *rsa); -+ - #if !defined(WITH_OPENSSL) && defined(ENABLE_PKCS11) - #undef ENABLE_PKCS11 - #endif -diff --color -ru -x regress -x autom4te.cache -x '*.o' -x '*.lo' -x Makefile -x config.status -x configure~ -x configure.ac openssh-9.3p1/ssh-rsa.c openssh-9.3p1-patched/ssh-rsa.c ---- openssh-9.3p1/ssh-rsa.c 2023-03-15 22:28:19.000000000 +0100 -+++ openssh-9.3p1-patched/ssh-rsa.c 2023-06-06 15:52:25.627551781 +0200 -@@ -23,6 +23,8 @@ - - #include - #include -+#include -+#include - - #include - #include -@@ -36,10 +36,13 @@ - #include "sshkey.h" - #include "digest.h" - #include "log.h" -+#ifdef ENABLE_PKCS11 -+#include "ssh-pkcs11.h" -+#endif - - #include "openbsd-compat/openssl-compat.h" - --static int openssh_RSA_verify(int, u_char *, size_t, u_char *, size_t, RSA *); -+static int openssh_RSA_verify(int, const u_char *, size_t, u_char *, size_t, EVP_PKEY *); - - static u_int - ssh_rsa_size(const struct sshkey *key) -@@ -131,27 +133,50 @@ - static int - ssh_rsa_generate(struct sshkey *k, int bits) - { -- RSA *private = NULL; -+ EVP_PKEY_CTX *ctx = NULL; -+ EVP_PKEY *res = NULL; - BIGNUM *f4 = NULL; - int ret = SSH_ERR_INTERNAL_ERROR; - - if (bits < SSH_RSA_MINIMUM_MODULUS_SIZE || - bits > SSHBUF_MAX_BIGNUM * 8) - return SSH_ERR_KEY_LENGTH; -- if ((private = RSA_new()) == NULL || (f4 = BN_new()) == NULL) { -+ -+ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL)) == NULL -+ || (f4 = BN_new()) == NULL || !BN_set_word(f4, RSA_F4)) { - ret = SSH_ERR_ALLOC_FAIL; - goto out; - } -- if (!BN_set_word(f4, RSA_F4) || -- !RSA_generate_key_ex(private, bits, f4, NULL)) { -+ -+ if (EVP_PKEY_keygen_init(ctx) <= 0) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ -+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0) { -+ ret = SSH_ERR_KEY_LENGTH; -+ goto out; -+ } -+ -+ if (EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, f4) <= 0) -+ goto out; -+ -+ if (EVP_PKEY_keygen(ctx, &res) <= 0) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ -+ /* This function is deprecated in OpenSSL 3.0 but OpenSSH doesn't worry about it*/ -+ k->rsa = EVP_PKEY_get1_RSA(res); -+ if (k->rsa) { -+ ret = 0; -+ } else { - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } -- k->rsa = private; -- private = NULL; -- ret = 0; - out: -- RSA_free(private); -+ EVP_PKEY_CTX_free(ctx); -+ EVP_PKEY_free(res); - BN_free(f4); - return ret; - } -@@ -317,21 +342,6 @@ - return -1; - } - --static int --rsa_hash_alg_nid(int type) --{ -- switch (type) { -- case SSH_DIGEST_SHA1: -- return NID_sha1; -- case SSH_DIGEST_SHA256: -- return NID_sha256; -- case SSH_DIGEST_SHA512: -- return NID_sha512; -- default: -- return -1; -- } --} -- - int - ssh_rsa_complete_crt_parameters(struct sshkey *key, const BIGNUM *iqmp) - { -@@ -393,11 +403,10 @@ - const u_char *data, size_t datalen, - const char *alg, const char *sk_provider, const char *sk_pin, u_int compat) - { -- const BIGNUM *rsa_n; -- u_char digest[SSH_DIGEST_MAX_LENGTH], *sig = NULL; -- size_t slen = 0; -- u_int hlen, len; -- int nid, hash_alg, ret = SSH_ERR_INTERNAL_ERROR; -+ EVP_PKEY *pkey = NULL; -+ u_char *sig = NULL; -+ int len, slen = 0; -+ int hash_alg, ret = SSH_ERR_INTERNAL_ERROR; - struct sshbuf *b = NULL; - - if (lenp != NULL) -@@ -409,33 +418,33 @@ - hash_alg = SSH_DIGEST_SHA1; - else - hash_alg = rsa_hash_id_from_keyname(alg); -+ - if (key == NULL || key->rsa == NULL || hash_alg == -1 || - sshkey_type_plain(key->type) != KEY_RSA) - return SSH_ERR_INVALID_ARGUMENT; -- RSA_get0_key(key->rsa, &rsa_n, NULL, NULL); -- if (BN_num_bits(rsa_n) < SSH_RSA_MINIMUM_MODULUS_SIZE) -- return SSH_ERR_KEY_LENGTH; - slen = RSA_size(key->rsa); -- if (slen <= 0 || slen > SSHBUF_MAX_BIGNUM) -- return SSH_ERR_INVALID_ARGUMENT; -- -- /* hash the data */ -- nid = rsa_hash_alg_nid(hash_alg); -- if ((hlen = ssh_digest_bytes(hash_alg)) == 0) -- return SSH_ERR_INTERNAL_ERROR; -- if ((ret = ssh_digest_memory(hash_alg, data, datalen, -- digest, sizeof(digest))) != 0) -- goto out; -+ if (RSA_bits(key->rsa) < SSH_RSA_MINIMUM_MODULUS_SIZE) -+ return SSH_ERR_KEY_LENGTH; - -- if ((sig = malloc(slen)) == NULL) { -- ret = SSH_ERR_ALLOC_FAIL; -- goto out; -+#ifdef ENABLE_PKCS11 -+ if (is_rsa_pkcs11(key->rsa)) { -+ if ((pkey = EVP_PKEY_new()) == NULL || -+ EVP_PKEY_set1_RSA(pkey, key->rsa) != 1) -+ return SSH_ERR_ALLOC_FAIL; -+ } else { -+#endif -+ if ((ret = ssh_create_evp_rsa(key, &pkey)) != 0) -+ return ret; -+#ifdef ENABLE_PKCS11 - } -- -- if (RSA_sign(nid, digest, hlen, sig, &len, key->rsa) != 1) { -- ret = SSH_ERR_LIBCRYPTO_ERROR; -+#endif -+ ret = sshkey_calculate_signature(pkey, hash_alg, &sig, &len, data, -+ datalen); -+ EVP_PKEY_free(pkey); -+ if (ret < 0) { - goto out; - } -+ - if (len < slen) { - size_t diff = slen - len; - memmove(sig + diff, sig, len); -@@ -444,6 +453,7 @@ - ret = SSH_ERR_INTERNAL_ERROR; - goto out; - } -+ - /* encode signature */ - if ((b = sshbuf_new()) == NULL) { - ret = SSH_ERR_ALLOC_FAIL; -@@ -464,7 +474,6 @@ - *lenp = len; - ret = 0; - out: -- explicit_bzero(digest, sizeof(digest)); - freezero(sig, slen); - sshbuf_free(b); - return ret; -@@ -476,10 +485,10 @@ - const u_char *data, size_t dlen, const char *alg, u_int compat, - struct sshkey_sig_details **detailsp) - { -- const BIGNUM *rsa_n; -+ EVP_PKEY *pkey = NULL; - char *sigtype = NULL; - int hash_alg, want_alg, ret = SSH_ERR_INTERNAL_ERROR; -- size_t len = 0, diff, modlen, hlen; -+ size_t len = 0, diff, modlen; - struct sshbuf *b = NULL; - u_char digest[SSH_DIGEST_MAX_LENGTH], *osigblob, *sigblob = NULL; - -@@ -487,8 +496,7 @@ - sshkey_type_plain(key->type) != KEY_RSA || - sig == NULL || siglen == 0) - return SSH_ERR_INVALID_ARGUMENT; -- RSA_get0_key(key->rsa, &rsa_n, NULL, NULL); -- if (BN_num_bits(rsa_n) < SSH_RSA_MINIMUM_MODULUS_SIZE) -+ if (RSA_bits(key->rsa) < SSH_RSA_MINIMUM_MODULUS_SIZE) - return SSH_ERR_KEY_LENGTH; - - if ((b = sshbuf_from(sig, siglen)) == NULL) -@@ -540,16 +548,13 @@ - explicit_bzero(sigblob, diff); - len = modlen; - } -- if ((hlen = ssh_digest_bytes(hash_alg)) == 0) { -- ret = SSH_ERR_INTERNAL_ERROR; -- goto out; -- } -- if ((ret = ssh_digest_memory(hash_alg, data, dlen, -- digest, sizeof(digest))) != 0) -+ -+ if ((ret = ssh_create_evp_rsa(key, &pkey)) != 0) - goto out; - -- ret = openssh_RSA_verify(hash_alg, digest, hlen, sigblob, len, -- key->rsa); -+ ret = openssh_RSA_verify(hash_alg, data, dlen, sigblob, len, pkey); -+ EVP_PKEY_free(pkey); -+ - out: - freezero(sigblob, len); - free(sigtype); -@@ -558,125 +563,110 @@ - return ret; - } - --/* -- * See: -- * http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/ -- * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.asn -- */ -- --/* -- * id-sha1 OBJECT IDENTIFIER ::= { iso(1) identified-organization(3) -- * oiw(14) secsig(3) algorithms(2) 26 } -- */ --static const u_char id_sha1[] = { -- 0x30, 0x21, /* type Sequence, length 0x21 (33) */ -- 0x30, 0x09, /* type Sequence, length 0x09 */ -- 0x06, 0x05, /* type OID, length 0x05 */ -- 0x2b, 0x0e, 0x03, 0x02, 0x1a, /* id-sha1 OID */ -- 0x05, 0x00, /* NULL */ -- 0x04, 0x14 /* Octet string, length 0x14 (20), followed by sha1 hash */ --}; -- --/* -- * See http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html -- * id-sha256 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2) country(16) us(840) -- * organization(1) gov(101) csor(3) nistAlgorithm(4) hashAlgs(2) -- * id-sha256(1) } -- */ --static const u_char id_sha256[] = { -- 0x30, 0x31, /* type Sequence, length 0x31 (49) */ -- 0x30, 0x0d, /* type Sequence, length 0x0d (13) */ -- 0x06, 0x09, /* type OID, length 0x09 */ -- 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, /* id-sha256 */ -- 0x05, 0x00, /* NULL */ -- 0x04, 0x20 /* Octet string, length 0x20 (32), followed by sha256 hash */ --}; -- --/* -- * See http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html -- * id-sha512 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2) country(16) us(840) -- * organization(1) gov(101) csor(3) nistAlgorithm(4) hashAlgs(2) -- * id-sha256(3) } -- */ --static const u_char id_sha512[] = { -- 0x30, 0x51, /* type Sequence, length 0x51 (81) */ -- 0x30, 0x0d, /* type Sequence, length 0x0d (13) */ -- 0x06, 0x09, /* type OID, length 0x09 */ -- 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, /* id-sha512 */ -- 0x05, 0x00, /* NULL */ -- 0x04, 0x40 /* Octet string, length 0x40 (64), followed by sha512 hash */ --}; -- - static int --rsa_hash_alg_oid(int hash_alg, const u_char **oidp, size_t *oidlenp) -+openssh_RSA_verify(int hash_alg, const u_char *data, size_t datalen, -+ u_char *sigbuf, size_t siglen, EVP_PKEY *pkey) - { -- switch (hash_alg) { -- case SSH_DIGEST_SHA1: -- *oidp = id_sha1; -- *oidlenp = sizeof(id_sha1); -- break; -- case SSH_DIGEST_SHA256: -- *oidp = id_sha256; -- *oidlenp = sizeof(id_sha256); -- break; -- case SSH_DIGEST_SHA512: -- *oidp = id_sha512; -- *oidlenp = sizeof(id_sha512); -- break; -- default: -- return SSH_ERR_INVALID_ARGUMENT; -- } -- return 0; --} -+ size_t rsasize = 0; -+ int ret; - --static int --openssh_RSA_verify(int hash_alg, u_char *hash, size_t hashlen, -- u_char *sigbuf, size_t siglen, RSA *rsa) --{ -- size_t rsasize = 0, oidlen = 0, hlen = 0; -- int ret, len, oidmatch, hashmatch; -- const u_char *oid = NULL; -- u_char *decrypted = NULL; -- -- if ((ret = rsa_hash_alg_oid(hash_alg, &oid, &oidlen)) != 0) -- return ret; -- ret = SSH_ERR_INTERNAL_ERROR; -- hlen = ssh_digest_bytes(hash_alg); -- if (hashlen != hlen) { -- ret = SSH_ERR_INVALID_ARGUMENT; -- goto done; -- } -- rsasize = RSA_size(rsa); -+ rsasize = EVP_PKEY_get_size(pkey); - if (rsasize <= 0 || rsasize > SSHBUF_MAX_BIGNUM || - siglen == 0 || siglen > rsasize) { - ret = SSH_ERR_INVALID_ARGUMENT; - goto done; - } -- if ((decrypted = malloc(rsasize)) == NULL) { -- ret = SSH_ERR_ALLOC_FAIL; -- goto done; -- } -- if ((len = RSA_public_decrypt(siglen, sigbuf, decrypted, rsa, -- RSA_PKCS1_PADDING)) < 0) { -- ret = SSH_ERR_LIBCRYPTO_ERROR; -- goto done; -- } -- if (len < 0 || (size_t)len != hlen + oidlen) { -- ret = SSH_ERR_INVALID_FORMAT; -- goto done; -- } -- oidmatch = timingsafe_bcmp(decrypted, oid, oidlen) == 0; -- hashmatch = timingsafe_bcmp(decrypted + oidlen, hash, hlen) == 0; -- if (!oidmatch || !hashmatch) { -- ret = SSH_ERR_SIGNATURE_INVALID; -- goto done; -- } -- ret = 0; -+ -+ ret = sshkey_verify_signature(pkey, hash_alg, data, datalen, -+ sigbuf, siglen); -+ - done: -- freezero(decrypted, rsasize); - return ret; - } - -+int -+ssh_create_evp_rsa(const struct sshkey *k, EVP_PKEY **pkey) -+{ -+ OSSL_PARAM_BLD *param_bld = NULL; -+ EVP_PKEY_CTX *ctx = NULL; -+ int ret = 0; -+ const BIGNUM *n = NULL, *e = NULL, *d = NULL, *p = NULL, *q = NULL; -+ const BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL; -+ -+ if (k == NULL) -+ return SSH_ERR_INVALID_ARGUMENT; -+ if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL)) == NULL || -+ (param_bld = OSSL_PARAM_BLD_new()) == NULL) { -+ ret = SSH_ERR_ALLOC_FAIL; -+ goto out; -+ } -+ -+ RSA_get0_key(k->rsa, &n, &e, &d); -+ RSA_get0_factors(k->rsa, &p, &q); -+ RSA_get0_crt_params(k->rsa, &dmp1, &dmq1, &iqmp); -+ -+ if (n != NULL && -+ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, n) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (e != NULL && -+ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, e) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (d != NULL && -+ OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_D, d) != 1) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ -+ if ((*pkey = sshkey_create_evp(param_bld, ctx)) == NULL) { -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ -+ /* setting this to param_build makes the creation process fail */ -+ if (p != NULL && -+ EVP_PKEY_set_bn_param(*pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, p) != 1) { -+ debug2_f("failed to add 'p' param"); -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (q != NULL && -+ EVP_PKEY_set_bn_param(*pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, q) != 1) { -+ debug2_f("failed to add 'q' param"); -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (dmp1 != NULL && -+ EVP_PKEY_set_bn_param(*pkey, -+ OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1) != 1) { -+ debug2_f("failed to add 'dmp1' param"); -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (dmq1 != NULL && -+ EVP_PKEY_set_bn_param(*pkey, -+ OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1) != 1) { -+ debug2_f("failed to add 'dmq1' param"); -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ if (iqmp != NULL && -+ EVP_PKEY_set_bn_param(*pkey, -+ OSSL_PKEY_PARAM_RSA_COEFFICIENT1, iqmp) != 1) { -+ debug2_f("failed to add 'iqmp' param"); -+ ret = SSH_ERR_LIBCRYPTO_ERROR; -+ goto out; -+ } -+ -+out: -+ OSSL_PARAM_BLD_free(param_bld); -+ EVP_PKEY_CTX_free(ctx); -+ return ret; -+} -+ - static const struct sshkey_impl_funcs sshkey_rsa_funcs = { - /* .size = */ ssh_rsa_size, - /* .alloc = */ ssh_rsa_alloc, diff --git a/openssh-9.6p1-gssapi-keyex.patch b/openssh-9.6p1-gssapi-keyex.patch new file mode 100644 index 0000000..2fb5514 --- /dev/null +++ b/openssh-9.6p1-gssapi-keyex.patch @@ -0,0 +1,4187 @@ +diff --color -ruNp a/auth2.c b/auth2.c +--- a/auth2.c 2024-09-16 11:45:56.858133241 +0200 ++++ b/auth2.c 2024-09-16 11:46:34.688939755 +0200 +@@ -71,6 +71,7 @@ extern Authmethod method_passwd; + extern Authmethod method_kbdint; + extern Authmethod method_hostbased; + #ifdef GSSAPI ++extern Authmethod method_gsskeyex; + extern Authmethod method_gssapi; + #endif + +@@ -78,6 +79,7 @@ Authmethod *authmethods[] = { + &method_none, + &method_pubkey, + #ifdef GSSAPI ++ &method_gsskeyex, + &method_gssapi, + #endif + &method_passwd, +diff --color -ruNp a/auth2-gss.c b/auth2-gss.c +--- a/auth2-gss.c 2024-09-16 11:45:56.858133241 +0200 ++++ b/auth2-gss.c 2024-09-16 11:46:34.689939776 +0200 +@@ -51,6 +51,7 @@ + #define SSH_GSSAPI_MAX_MECHS 2048 + + extern ServerOptions options; ++extern struct authmethod_cfg methodcfg_gsskeyex; + extern struct authmethod_cfg methodcfg_gssapi; + + static int input_gssapi_token(int type, u_int32_t plen, struct ssh *ssh); +@@ -59,6 +60,48 @@ static int input_gssapi_exchange_complet + static int input_gssapi_errtok(int, u_int32_t, struct ssh *); + + /* ++ * The 'gssapi_keyex' userauth mechanism. ++ */ ++static int ++userauth_gsskeyex(struct ssh *ssh, const char *method) ++{ ++ Authctxt *authctxt = ssh->authctxt; ++ int r, authenticated = 0; ++ struct sshbuf *b = NULL; ++ gss_buffer_desc mic, gssbuf; ++ u_char *p; ++ size_t len; ++ ++ if ((r = sshpkt_get_string(ssh, &p, &len)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal_fr(r, "parsing"); ++ ++ if ((b = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new failed"); ++ ++ mic.value = p; ++ mic.length = len; ++ ++ ssh_gssapi_buildmic(b, authctxt->user, authctxt->service, ++ "gssapi-keyex", ssh->kex->session_id); ++ ++ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL) ++ fatal_f("sshbuf_mutable_ptr failed"); ++ gssbuf.length = sshbuf_len(b); ++ ++ /* gss_kex_context is NULL with privsep, so we can't check it here */ ++ if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gss_kex_context, ++ &gssbuf, &mic))) ++ authenticated = mm_ssh_gssapi_userok(authctxt->user, ++ authctxt->pw, 1); ++ ++ sshbuf_free(b); ++ free(mic.value); ++ ++ return (authenticated); ++} ++ ++/* + * We only support those mechanisms that we know about (ie ones that we know + * how to check local user kuserok and the like) + */ +@@ -267,7 +310,7 @@ input_gssapi_exchange_complete(int type, + if ((r = sshpkt_get_end(ssh)) != 0) + fatal_fr(r, "parse packet"); + +- authenticated = mm_ssh_gssapi_userok(authctxt->user); ++ authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 1); + + authctxt->postponed = 0; + ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); +@@ -315,7 +358,7 @@ input_gssapi_mic(int type, u_int32_t ple + gssbuf.length = sshbuf_len(b); + + if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic))) +- authenticated = mm_ssh_gssapi_userok(authctxt->user); ++ authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 0); + else + logit("GSSAPI MIC check failed"); + +@@ -333,6 +376,11 @@ input_gssapi_mic(int type, u_int32_t ple + return 0; + } + ++Authmethod method_gsskeyex = { ++ &methodcfg_gsskeyex, ++ userauth_gsskeyex, ++}; ++ + Authmethod method_gssapi = { + &methodcfg_gssapi, + userauth_gssapi, +diff --color -ruNp a/auth2-methods.c b/auth2-methods.c +--- a/auth2-methods.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/auth2-methods.c 2024-09-16 11:46:34.689939776 +0200 +@@ -50,6 +50,11 @@ struct authmethod_cfg methodcfg_pubkey = + &options.pubkey_authentication + }; + #ifdef GSSAPI ++struct authmethod_cfg methodcfg_gsskeyex = { ++ "gssapi-keyex", ++ NULL, ++ &options.gss_authentication ++}; + struct authmethod_cfg methodcfg_gssapi = { + "gssapi-with-mic", + NULL, +@@ -76,6 +81,7 @@ static struct authmethod_cfg *authmethod + &methodcfg_none, + &methodcfg_pubkey, + #ifdef GSSAPI ++ &methodcfg_gsskeyex, + &methodcfg_gssapi, + #endif + &methodcfg_passwd, +diff --color -ruNp a/auth.c b/auth.c +--- a/auth.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/auth.c 2024-09-16 11:46:34.690939798 +0200 +@@ -356,7 +356,8 @@ auth_root_allowed(struct ssh *ssh, const + case PERMIT_NO_PASSWD: + if (strcmp(method, "publickey") == 0 || + strcmp(method, "hostbased") == 0 || +- strcmp(method, "gssapi-with-mic") == 0) ++ strcmp(method, "gssapi-with-mic") == 0 || ++ strcmp(method, "gssapi-keyex") == 0) + return 1; + break; + case PERMIT_FORCED_ONLY: +diff --color -ruNp a/canohost.c b/canohost.c +--- a/canohost.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/canohost.c 2024-09-16 11:46:34.690939798 +0200 +@@ -35,6 +35,99 @@ + #include "canohost.h" + #include "misc.h" + ++/* ++ * Returns the remote DNS hostname as a string. The returned string must not ++ * be freed. NB. this will usually trigger a DNS query the first time it is ++ * called. ++ * This function does additional checks on the hostname to mitigate some ++ * attacks on legacy rhosts-style authentication. ++ * XXX is RhostsRSAAuthentication vulnerable to these? ++ * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?) ++ */ ++ ++char * ++remote_hostname(struct ssh *ssh) ++{ ++ struct sockaddr_storage from; ++ socklen_t fromlen; ++ struct addrinfo hints, *ai, *aitop; ++ char name[NI_MAXHOST], ntop2[NI_MAXHOST]; ++ const char *ntop = ssh_remote_ipaddr(ssh); ++ ++ /* Get IP address of client. */ ++ fromlen = sizeof(from); ++ memset(&from, 0, sizeof(from)); ++ if (getpeername(ssh_packet_get_connection_in(ssh), ++ (struct sockaddr *)&from, &fromlen) == -1) { ++ debug("getpeername failed: %.100s", strerror(errno)); ++ return xstrdup(ntop); ++ } ++ ++ ipv64_normalise_mapped(&from, &fromlen); ++ if (from.ss_family == AF_INET6) ++ fromlen = sizeof(struct sockaddr_in6); ++ ++ debug3("Trying to reverse map address %.100s.", ntop); ++ /* Map the IP address to a host name. */ ++ if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name), ++ NULL, 0, NI_NAMEREQD) != 0) { ++ /* Host name not found. Use ip address. */ ++ return xstrdup(ntop); ++ } ++ ++ /* ++ * if reverse lookup result looks like a numeric hostname, ++ * someone is trying to trick us by PTR record like following: ++ * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5 ++ */ ++ memset(&hints, 0, sizeof(hints)); ++ hints.ai_socktype = SOCK_DGRAM; /*dummy*/ ++ hints.ai_flags = AI_NUMERICHOST; ++ if (getaddrinfo(name, NULL, &hints, &ai) == 0) { ++ logit("Nasty PTR record \"%s\" is set up for %s, ignoring", ++ name, ntop); ++ freeaddrinfo(ai); ++ return xstrdup(ntop); ++ } ++ ++ /* Names are stored in lowercase. */ ++ lowercase(name); ++ ++ /* ++ * Map it back to an IP address and check that the given ++ * address actually is an address of this host. This is ++ * necessary because anyone with access to a name server can ++ * define arbitrary names for an IP address. Mapping from ++ * name to IP address can be trusted better (but can still be ++ * fooled if the intruder has access to the name server of ++ * the domain). ++ */ ++ memset(&hints, 0, sizeof(hints)); ++ hints.ai_family = from.ss_family; ++ hints.ai_socktype = SOCK_STREAM; ++ if (getaddrinfo(name, NULL, &hints, &aitop) != 0) { ++ logit("reverse mapping checking getaddrinfo for %.700s " ++ "[%s] failed.", name, ntop); ++ return xstrdup(ntop); ++ } ++ /* Look for the address from the list of addresses. */ ++ for (ai = aitop; ai; ai = ai->ai_next) { ++ if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2, ++ sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 && ++ (strcmp(ntop, ntop2) == 0)) ++ break; ++ } ++ freeaddrinfo(aitop); ++ /* If we reached the end of the list, the address was not there. */ ++ if (ai == NULL) { ++ /* Address not found for the host name. */ ++ logit("Address %.100s maps to %.600s, but this does not " ++ "map back to the address.", ntop, name); ++ return xstrdup(ntop); ++ } ++ return xstrdup(name); ++} ++ + void + ipv64_normalise_mapped(struct sockaddr_storage *addr, socklen_t *len) + { +diff --color -ruNp a/canohost.h b/canohost.h +--- a/canohost.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/canohost.h 2024-09-16 11:46:34.690939798 +0200 +@@ -15,6 +15,9 @@ + #ifndef _CANOHOST_H + #define _CANOHOST_H + ++struct ssh; ++ ++char *remote_hostname(struct ssh *); + char *get_peer_ipaddr(int); + int get_peer_port(int); + char *get_local_ipaddr(int); +diff --color -ruNp a/clientloop.c b/clientloop.c +--- a/clientloop.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/clientloop.c 2024-09-16 11:46:34.690939798 +0200 +@@ -115,6 +115,10 @@ + #include "ssherr.h" + #include "hostfile.h" + ++#ifdef GSSAPI ++#include "ssh-gss.h" ++#endif ++ + /* Permitted RSA signature algorithms for UpdateHostkeys proofs */ + #define HOSTKEY_PROOF_RSA_ALGS "rsa-sha2-512,rsa-sha2-256" + +@@ -1590,6 +1594,14 @@ client_loop(struct ssh *ssh, int have_pt + /* Do channel operations. */ + channel_after_poll(ssh, pfd, npfd_active); + ++#ifdef GSSAPI ++ if (options.gss_renewal_rekey && ++ ssh_gssapi_credentials_updated(NULL)) { ++ debug("credentials updated - forcing rekey"); ++ need_rekeying = 1; ++ } ++#endif ++ + /* Buffer input from the connection. */ + if (conn_in_ready) + client_process_net_input(ssh); +diff --color -ruNp a/configure.ac b/configure.ac +--- a/configure.ac 2024-09-16 11:45:56.870133497 +0200 ++++ b/configure.ac 2024-09-16 11:46:34.691939819 +0200 +@@ -774,6 +774,30 @@ int main(void) { if (NSVersionOfRunTimeL + [Use tunnel device compatibility to OpenBSD]) + AC_DEFINE([SSH_TUN_PREPEND_AF], [1], + [Prepend the address family to IP tunnel traffic]) ++ AC_MSG_CHECKING([if we have the Security Authorization Session API]) ++ AC_TRY_COMPILE([#include ], ++ [SessionCreate(0, 0);], ++ [ac_cv_use_security_session_api="yes" ++ AC_DEFINE([USE_SECURITY_SESSION_API], [1], ++ [platform has the Security Authorization Session API]) ++ LIBS="$LIBS -framework Security" ++ AC_MSG_RESULT([yes])], ++ [ac_cv_use_security_session_api="no" ++ AC_MSG_RESULT([no])]) ++ AC_MSG_CHECKING([if we have an in-memory credentials cache]) ++ AC_TRY_COMPILE( ++ [#include ], ++ [cc_context_t c; ++ (void) cc_initialize (&c, 0, NULL, NULL);], ++ [AC_DEFINE([USE_CCAPI], [1], ++ [platform uses an in-memory credentials cache]) ++ LIBS="$LIBS -framework Security" ++ AC_MSG_RESULT([yes]) ++ if test "x$ac_cv_use_security_session_api" = "xno"; then ++ AC_MSG_ERROR([*** Need a security framework to use the credentials cache API ***]) ++ fi], ++ [AC_MSG_RESULT([no])] ++ ) + m4_pattern_allow([AU_IPv]) + AC_CHECK_DECL([AU_IPv4], [], + AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records]) +diff --color -ruNp a/gss-genr.c b/gss-genr.c +--- a/gss-genr.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/gss-genr.c 2024-09-16 11:46:34.708940181 +0200 +@@ -42,9 +42,33 @@ + #include "sshbuf.h" + #include "log.h" + #include "ssh2.h" ++#include "cipher.h" ++#include "sshkey.h" ++#include "kex.h" ++#include "digest.h" ++#include "packet.h" + + #include "ssh-gss.h" + ++typedef struct { ++ char *encoded; ++ gss_OID oid; ++} ssh_gss_kex_mapping; ++ ++/* ++ * XXX - It would be nice to find a more elegant way of handling the ++ * XXX passing of the key exchange context to the userauth routines ++ */ ++ ++Gssctxt *gss_kex_context = NULL; ++ ++static ssh_gss_kex_mapping *gss_enc2oid = NULL; ++ ++int ++ssh_gssapi_oid_table_ok(void) { ++ return (gss_enc2oid != NULL); ++} ++ + /* sshbuf_get for gss_buffer_desc */ + int + ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g) +@@ -60,6 +84,159 @@ ssh_gssapi_get_buffer_desc(struct sshbuf + return 0; + } + ++/* sshpkt_get of gss_buffer_desc */ ++int ++ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *ssh, gss_buffer_desc *g) ++{ ++ int r; ++ u_char *p; ++ size_t len; ++ ++ if ((r = sshpkt_get_string(ssh, &p, &len)) != 0) ++ return r; ++ g->value = p; ++ g->length = len; ++ return 0; ++} ++ ++/* ++ * Return a list of the gss-group1-sha1 mechanisms supported by this program ++ * ++ * We test mechanisms to ensure that we can use them, to avoid starting ++ * a key exchange with a bad mechanism ++ */ ++ ++char * ++ssh_gssapi_client_mechanisms(const char *host, const char *client, ++ const char *kex) { ++ gss_OID_set gss_supported = NULL; ++ OM_uint32 min_status; ++ ++ if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported))) ++ return NULL; ++ ++ return ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism, ++ host, client, kex); ++} ++ ++char * ++ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check, ++ const char *host, const char *client, const char *kex) { ++ struct sshbuf *buf = NULL; ++ size_t i; ++ int r = SSH_ERR_ALLOC_FAIL; ++ int oidpos, enclen; ++ char *mechs, *encoded; ++ u_char digest[SSH_DIGEST_MAX_LENGTH]; ++ char deroid[2]; ++ struct ssh_digest_ctx *md = NULL; ++ char *s, *cp, *p; ++ ++ if (gss_enc2oid != NULL) { ++ for (i = 0; gss_enc2oid[i].encoded != NULL; i++) ++ free(gss_enc2oid[i].encoded); ++ free(gss_enc2oid); ++ } ++ ++ gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) * ++ (gss_supported->count + 1)); ++ ++ if ((buf = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new failed"); ++ ++ oidpos = 0; ++ s = cp = xstrdup(kex); ++ for (i = 0; i < gss_supported->count; i++) { ++ if (gss_supported->elements[i].length < 128 && ++ (*check)(NULL, &(gss_supported->elements[i]), host, client)) { ++ ++ deroid[0] = SSH_GSS_OIDTYPE; ++ deroid[1] = gss_supported->elements[i].length; ++ ++ if ((md = ssh_digest_start(SSH_DIGEST_MD5)) == NULL || ++ (r = ssh_digest_update(md, deroid, 2)) != 0 || ++ (r = ssh_digest_update(md, ++ gss_supported->elements[i].elements, ++ gss_supported->elements[i].length)) != 0 || ++ (r = ssh_digest_final(md, digest, sizeof(digest))) != 0) ++ fatal_fr(r, "digest failed"); ++ ssh_digest_free(md); ++ md = NULL; ++ ++ encoded = xmalloc(ssh_digest_bytes(SSH_DIGEST_MD5) ++ * 2); ++ enclen = __b64_ntop(digest, ++ ssh_digest_bytes(SSH_DIGEST_MD5), encoded, ++ ssh_digest_bytes(SSH_DIGEST_MD5) * 2); ++ ++ cp = strncpy(s, kex, strlen(kex)); ++ for ((p = strsep(&cp, ",")); p && *p != '\0'; ++ (p = strsep(&cp, ","))) { ++ if (sshbuf_len(buf) != 0 && ++ (r = sshbuf_put_u8(buf, ',')) != 0) ++ fatal_fr(r, "sshbuf_put_u8 error"); ++ if ((r = sshbuf_put(buf, p, strlen(p))) != 0 || ++ (r = sshbuf_put(buf, encoded, enclen)) != 0) ++ fatal_fr(r, "sshbuf_put error"); ++ } ++ ++ gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]); ++ gss_enc2oid[oidpos].encoded = encoded; ++ oidpos++; ++ } ++ } ++ free(s); ++ gss_enc2oid[oidpos].oid = NULL; ++ gss_enc2oid[oidpos].encoded = NULL; ++ ++ if ((mechs = sshbuf_dup_string(buf)) == NULL) ++ fatal_f("sshbuf_dup_string failed"); ++ ++ sshbuf_free(buf); ++ ++ if (strlen(mechs) == 0) { ++ free(mechs); ++ mechs = NULL; ++ } ++ ++ return (mechs); ++} ++ ++gss_OID ++ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) { ++ int i = 0; ++ ++#define SKIP_KEX_NAME(type) \ ++ case type: \ ++ if (strlen(name) < sizeof(type##_ID)) \ ++ return GSS_C_NO_OID; \ ++ name += sizeof(type##_ID) - 1; \ ++ break; ++ ++ switch (kex_type) { ++ SKIP_KEX_NAME(KEX_GSS_GRP1_SHA1) ++ SKIP_KEX_NAME(KEX_GSS_GRP14_SHA1) ++ SKIP_KEX_NAME(KEX_GSS_GRP14_SHA256) ++ SKIP_KEX_NAME(KEX_GSS_GRP16_SHA512) ++ SKIP_KEX_NAME(KEX_GSS_GEX_SHA1) ++ SKIP_KEX_NAME(KEX_GSS_NISTP256_SHA256) ++ SKIP_KEX_NAME(KEX_GSS_C25519_SHA256) ++ default: ++ return GSS_C_NO_OID; ++ } ++ ++#undef SKIP_KEX_NAME ++ ++ while (gss_enc2oid[i].encoded != NULL && ++ strcmp(name, gss_enc2oid[i].encoded) != 0) ++ i++; ++ ++ if (gss_enc2oid[i].oid != NULL && ctx != NULL) ++ ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid); ++ ++ return gss_enc2oid[i].oid; ++} ++ + /* Check that the OID in a data stream matches that in the context */ + int + ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) +@@ -168,6 +345,7 @@ ssh_gssapi_build_ctx(Gssctxt **ctx) + (*ctx)->creds = GSS_C_NO_CREDENTIAL; + (*ctx)->client = GSS_C_NO_NAME; + (*ctx)->client_creds = GSS_C_NO_CREDENTIAL; ++ (*ctx)->first = 1; + } + + /* Delete our context, providing it has been built correctly */ +@@ -193,6 +371,12 @@ ssh_gssapi_delete_ctx(Gssctxt **ctx) + gss_release_name(&ms, &(*ctx)->client); + if ((*ctx)->client_creds != GSS_C_NO_CREDENTIAL) + gss_release_cred(&ms, &(*ctx)->client_creds); ++ sshbuf_free((*ctx)->shared_secret); ++ sshbuf_free((*ctx)->server_pubkey); ++ sshbuf_free((*ctx)->server_host_key_blob); ++ sshbuf_free((*ctx)->server_blob); ++ explicit_bzero((*ctx)->hash, sizeof((*ctx)->hash)); ++ BN_clear_free((*ctx)->dh_client_pub); + + free(*ctx); + *ctx = NULL; +@@ -216,7 +400,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int de + } + + ctx->major = gss_init_sec_context(&ctx->minor, +- GSS_C_NO_CREDENTIAL, &ctx->context, ctx->name, ctx->oid, ++ ctx->client_creds, &ctx->context, ctx->name, ctx->oid, + GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, + 0, NULL, recv_tok, NULL, send_tok, flags, NULL); + +@@ -246,8 +430,42 @@ ssh_gssapi_import_name(Gssctxt *ctx, con + } + + OM_uint32 ++ssh_gssapi_client_identity(Gssctxt *ctx, const char *name) ++{ ++ gss_buffer_desc gssbuf; ++ gss_name_t gssname; ++ OM_uint32 status; ++ gss_OID_set oidset; ++ ++ gssbuf.value = (void *) name; ++ gssbuf.length = strlen(gssbuf.value); ++ ++ gss_create_empty_oid_set(&status, &oidset); ++ gss_add_oid_set_member(&status, ctx->oid, &oidset); ++ ++ ctx->major = gss_import_name(&ctx->minor, &gssbuf, ++ GSS_C_NT_USER_NAME, &gssname); ++ ++ if (!ctx->major) ++ ctx->major = gss_acquire_cred(&ctx->minor, ++ gssname, 0, oidset, GSS_C_INITIATE, ++ &ctx->client_creds, NULL, NULL); ++ ++ gss_release_name(&status, &gssname); ++ gss_release_oid_set(&status, &oidset); ++ ++ if (ctx->major) ++ ssh_gssapi_error(ctx); ++ ++ return(ctx->major); ++} ++ ++OM_uint32 + ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) + { ++ if (ctx == NULL) ++ return -1; ++ + if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, + GSS_C_QOP_DEFAULT, buffer, hash))) + ssh_gssapi_error(ctx); +@@ -255,6 +473,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer + return (ctx->major); + } + ++/* Priviledged when used by server */ ++OM_uint32 ++ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) ++{ ++ if (ctx == NULL) ++ return -1; ++ ++ ctx->major = gss_verify_mic(&ctx->minor, ctx->context, ++ gssbuf, gssmic, NULL); ++ ++ return (ctx->major); ++} ++ + void + ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service, + const char *context, const struct sshbuf *session_id) +@@ -271,11 +502,16 @@ ssh_gssapi_buildmic(struct sshbuf *b, co + } + + int +-ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) ++ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host, ++ const char *client) + { + gss_buffer_desc token = GSS_C_EMPTY_BUFFER; + OM_uint32 major, minor; + gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; ++ Gssctxt *intctx = NULL; ++ ++ if (ctx == NULL) ++ ctx = &intctx; + + /* RFC 4462 says we MUST NOT do SPNEGO */ + if (oid->length == spnego_oid.length && +@@ -285,6 +521,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx + ssh_gssapi_build_ctx(ctx); + ssh_gssapi_set_oid(*ctx, oid); + major = ssh_gssapi_import_name(*ctx, host); ++ ++ if (!GSS_ERROR(major) && client) ++ major = ssh_gssapi_client_identity(*ctx, client); ++ + if (!GSS_ERROR(major)) { + major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, + NULL); +@@ -294,10 +534,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx + GSS_C_NO_BUFFER); + } + +- if (GSS_ERROR(major)) ++ if (GSS_ERROR(major) || intctx != NULL) + ssh_gssapi_delete_ctx(ctx); + + return (!GSS_ERROR(major)); + } + ++int ++ssh_gssapi_credentials_updated(Gssctxt *ctxt) { ++ static gss_name_t saved_name = GSS_C_NO_NAME; ++ static OM_uint32 saved_lifetime = 0; ++ static gss_OID saved_mech = GSS_C_NO_OID; ++ static gss_name_t name; ++ static OM_uint32 last_call = 0; ++ OM_uint32 lifetime, now, major, minor; ++ int equal; ++ ++ now = time(NULL); ++ ++ if (ctxt) { ++ debug("Rekey has happened - updating saved versions"); ++ ++ if (saved_name != GSS_C_NO_NAME) ++ gss_release_name(&minor, &saved_name); ++ ++ major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, ++ &saved_name, &saved_lifetime, NULL, NULL); ++ ++ if (!GSS_ERROR(major)) { ++ saved_mech = ctxt->oid; ++ saved_lifetime+= now; ++ } else { ++ /* Handle the error */ ++ } ++ return 0; ++ } ++ ++ if (now - last_call < 10) ++ return 0; ++ ++ last_call = now; ++ ++ if (saved_mech == GSS_C_NO_OID) ++ return 0; ++ ++ major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, ++ &name, &lifetime, NULL, NULL); ++ if (major == GSS_S_CREDENTIALS_EXPIRED) ++ return 0; ++ else if (GSS_ERROR(major)) ++ return 0; ++ ++ major = gss_compare_name(&minor, saved_name, name, &equal); ++ gss_release_name(&minor, &name); ++ if (GSS_ERROR(major)) ++ return 0; ++ ++ if (equal && (saved_lifetime < lifetime + now - 10)) ++ return 1; ++ ++ return 0; ++} ++ + #endif /* GSSAPI */ +diff --color -ruNp a/gss-serv.c b/gss-serv.c +--- a/gss-serv.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/gss-serv.c 2024-09-16 11:46:34.692939840 +0200 +@@ -1,7 +1,7 @@ + /* $OpenBSD: gss-serv.c,v 1.32 2020/03/13 03:17:07 djm Exp $ */ + + /* +- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. ++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions +@@ -44,17 +44,19 @@ + #include "session.h" + #include "misc.h" + #include "servconf.h" ++#include "uidswap.h" + + #include "ssh-gss.h" ++#include "monitor_wrap.h" + + extern ServerOptions options; + + static ssh_gssapi_client gssapi_client = +- { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, +- GSS_C_NO_CREDENTIAL, NULL, {NULL, NULL, NULL, NULL}}; ++ { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL, ++ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0}; + + ssh_gssapi_mech gssapi_null_mech = +- { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL}; ++ { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL}; + + #ifdef KRB5 + extern ssh_gssapi_mech gssapi_kerberos_mech; +@@ -141,6 +143,29 @@ ssh_gssapi_server_ctx(Gssctxt **ctx, gss + } + + /* Unprivileged */ ++char * ++ssh_gssapi_server_mechanisms(void) { ++ if (supported_oids == NULL) ++ ssh_gssapi_prepare_supported_oids(); ++ return (ssh_gssapi_kex_mechs(supported_oids, ++ &ssh_gssapi_server_check_mech, NULL, NULL, ++ options.gss_kex_algorithms)); ++} ++ ++/* Unprivileged */ ++int ++ssh_gssapi_server_check_mech(Gssctxt **dum, gss_OID oid, const char *data, ++ const char *dummy) { ++ Gssctxt *ctx = NULL; ++ int res; ++ ++ res = !GSS_ERROR(mm_ssh_gssapi_server_ctx(&ctx, oid)); ++ ssh_gssapi_delete_ctx(&ctx); ++ ++ return (res); ++} ++ ++/* Unprivileged */ + void + ssh_gssapi_supported_oids(gss_OID_set *oidset) + { +@@ -150,7 +175,9 @@ ssh_gssapi_supported_oids(gss_OID_set *o + gss_OID_set supported; + + gss_create_empty_oid_set(&min_status, oidset); +- gss_indicate_mechs(&min_status, &supported); ++ ++ if (GSS_ERROR(gss_indicate_mechs(&min_status, &supported))) ++ return; + + while (supported_mechs[i]->name != NULL) { + if (GSS_ERROR(gss_test_oid_set_member(&min_status, +@@ -276,8 +303,48 @@ OM_uint32 + ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) + { + int i = 0; ++ int equal = 0; ++ gss_name_t new_name = GSS_C_NO_NAME; ++ gss_buffer_desc ename = GSS_C_EMPTY_BUFFER; ++ ++ if (options.gss_store_rekey && client->used && ctx->client_creds) { ++ if (client->mech->oid.length != ctx->oid->length || ++ (memcmp(client->mech->oid.elements, ++ ctx->oid->elements, ctx->oid->length) !=0)) { ++ debug("Rekeyed credentials have different mechanism"); ++ return GSS_S_COMPLETE; ++ } + +- gss_buffer_desc ename; ++ if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor, ++ ctx->client_creds, ctx->oid, &new_name, ++ NULL, NULL, NULL))) { ++ ssh_gssapi_error(ctx); ++ return (ctx->major); ++ } ++ ++ ctx->major = gss_compare_name(&ctx->minor, client->name, ++ new_name, &equal); ++ ++ if (GSS_ERROR(ctx->major)) { ++ ssh_gssapi_error(ctx); ++ return (ctx->major); ++ } ++ ++ if (!equal) { ++ debug("Rekeyed credentials have different name"); ++ return GSS_S_COMPLETE; ++ } ++ ++ debug("Marking rekeyed credentials for export"); ++ ++ gss_release_name(&ctx->minor, &client->name); ++ gss_release_cred(&ctx->minor, &client->creds); ++ client->name = new_name; ++ client->creds = ctx->client_creds; ++ ctx->client_creds = GSS_C_NO_CREDENTIAL; ++ client->updated = 1; ++ return GSS_S_COMPLETE; ++ } + + client->mech = NULL; + +@@ -292,6 +359,13 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g + if (client->mech == NULL) + return GSS_S_FAILURE; + ++ if (ctx->client_creds && ++ (ctx->major = gss_inquire_cred_by_mech(&ctx->minor, ++ ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) { ++ ssh_gssapi_error(ctx); ++ return (ctx->major); ++ } ++ + if ((ctx->major = gss_display_name(&ctx->minor, ctx->client, + &client->displayname, NULL))) { + ssh_gssapi_error(ctx); +@@ -309,6 +383,8 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g + return (ctx->major); + } + ++ gss_release_buffer(&ctx->minor, &ename); ++ + /* We can't copy this structure, so we just move the pointer to it */ + client->creds = ctx->client_creds; + ctx->client_creds = GSS_C_NO_CREDENTIAL; +@@ -319,11 +395,20 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g + void + ssh_gssapi_cleanup_creds(void) + { +- if (gssapi_client.store.filename != NULL) { +- /* Unlink probably isn't sufficient */ +- debug("removing gssapi cred file\"%s\"", +- gssapi_client.store.filename); +- unlink(gssapi_client.store.filename); ++ krb5_ccache ccache = NULL; ++ krb5_error_code problem; ++ ++ if (gssapi_client.store.data != NULL) { ++ if ((problem = krb5_cc_resolve(gssapi_client.store.data, gssapi_client.store.envval, &ccache))) { ++ debug_f("krb5_cc_resolve(): %.100s", ++ krb5_get_err_text(gssapi_client.store.data, problem)); ++ } else if ((problem = krb5_cc_destroy(gssapi_client.store.data, ccache))) { ++ debug_f("krb5_cc_destroy(): %.100s", ++ krb5_get_err_text(gssapi_client.store.data, problem)); ++ } else { ++ krb5_free_context(gssapi_client.store.data); ++ gssapi_client.store.data = NULL; ++ } + } + } + +@@ -356,19 +441,23 @@ ssh_gssapi_do_child(char ***envp, u_int + + /* Privileged */ + int +-ssh_gssapi_userok(char *user) ++ssh_gssapi_userok(char *user, struct passwd *pw, int kex) + { + OM_uint32 lmin; + ++ (void) kex; /* used in privilege separation */ ++ + if (gssapi_client.exportedname.length == 0 || + gssapi_client.exportedname.value == NULL) { + debug("No suitable client data"); + return 0; + } + if (gssapi_client.mech && gssapi_client.mech->userok) +- if ((*gssapi_client.mech->userok)(&gssapi_client, user)) ++ if ((*gssapi_client.mech->userok)(&gssapi_client, user)) { ++ gssapi_client.used = 1; ++ gssapi_client.store.owner = pw; + return 1; +- else { ++ } else { + /* Destroy delegated credentials if userok fails */ + gss_release_buffer(&lmin, &gssapi_client.displayname); + gss_release_buffer(&lmin, &gssapi_client.exportedname); +@@ -382,14 +471,85 @@ ssh_gssapi_userok(char *user) + return (0); + } + +-/* Privileged */ +-OM_uint32 +-ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) ++/* These bits are only used for rekeying. The unpriviledged child is running ++ * as the user, the monitor is root. ++ * ++ * In the child, we want to : ++ * *) Ask the monitor to store our credentials into the store we specify ++ * *) If it succeeds, maybe do a PAM update ++ */ ++ ++/* Stuff for PAM */ ++ ++#ifdef USE_PAM ++static int ssh_gssapi_simple_conv(int n, const struct pam_message **msg, ++ struct pam_response **resp, void *data) + { +- ctx->major = gss_verify_mic(&ctx->minor, ctx->context, +- gssbuf, gssmic, NULL); ++ return (PAM_CONV_ERR); ++} ++#endif + +- return (ctx->major); ++void ++ssh_gssapi_rekey_creds(void) { ++ int ok; ++#ifdef USE_PAM ++ int ret; ++ pam_handle_t *pamh = NULL; ++ struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL}; ++ char *envstr; ++#endif ++ ++ if (gssapi_client.store.filename == NULL && ++ gssapi_client.store.envval == NULL && ++ gssapi_client.store.envvar == NULL) ++ return; ++ ++ ok = mm_ssh_gssapi_update_creds(&gssapi_client.store); ++ ++ if (!ok) ++ return; ++ ++ debug("Rekeyed credentials stored successfully"); ++ ++ /* Actually managing to play with the ssh pam stack from here will ++ * be next to impossible. In any case, we may want different options ++ * for rekeying. So, use our own :) ++ */ ++#ifdef USE_PAM ++ ret = pam_start("sshd-rekey", gssapi_client.store.owner->pw_name, ++ &pamconv, &pamh); ++ if (ret) ++ return; ++ ++ xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar, ++ gssapi_client.store.envval); ++ ++ ret = pam_putenv(pamh, envstr); ++ if (!ret) ++ pam_setcred(pamh, PAM_REINITIALIZE_CRED); ++ pam_end(pamh, PAM_SUCCESS); ++#endif ++} ++ ++int ++ssh_gssapi_update_creds(ssh_gssapi_ccache *store) { ++ int ok = 0; ++ ++ /* Check we've got credentials to store */ ++ if (!gssapi_client.updated) ++ return 0; ++ ++ gssapi_client.updated = 0; ++ ++ temporarily_use_uid(gssapi_client.store.owner); ++ if (gssapi_client.mech && gssapi_client.mech->updatecreds) ++ ok = (*gssapi_client.mech->updatecreds)(store, &gssapi_client); ++ else ++ debug("No update function for this mechanism"); ++ ++ restore_uid(); ++ ++ return ok; + } + + /* Privileged */ +diff --color -ruNp a/gss-serv-krb5.c b/gss-serv-krb5.c +--- a/gss-serv-krb5.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/gss-serv-krb5.c 2024-09-16 11:46:34.692939840 +0200 +@@ -1,7 +1,7 @@ + /* $OpenBSD: gss-serv-krb5.c,v 1.9 2018/07/09 21:37:55 markus Exp $ */ + + /* +- * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. ++ * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions +@@ -120,7 +120,7 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl + krb5_error_code problem; + krb5_principal princ; + OM_uint32 maj_status, min_status; +- int len; ++ const char *new_ccname, *new_cctype; + const char *errmsg; + + if (client->creds == NULL) { +@@ -180,11 +180,26 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl + return; + } + +- client->store.filename = xstrdup(krb5_cc_get_name(krb_context, ccache)); ++ new_cctype = krb5_cc_get_type(krb_context, ccache); ++ new_ccname = krb5_cc_get_name(krb_context, ccache); ++ + client->store.envvar = "KRB5CCNAME"; +- len = strlen(client->store.filename) + 6; +- client->store.envval = xmalloc(len); +- snprintf(client->store.envval, len, "FILE:%s", client->store.filename); ++#ifdef USE_CCAPI ++ xasprintf(&client->store.envval, "API:%s", new_ccname); ++ client->store.filename = NULL; ++#else ++ if (new_ccname[0] == ':') ++ new_ccname++; ++ xasprintf(&client->store.envval, "%s:%s", new_cctype, new_ccname); ++ if (strcmp(new_cctype, "DIR") == 0) { ++ char *p; ++ p = strrchr(client->store.envval, '/'); ++ if (p) ++ *p = '\0'; ++ } ++ if ((strcmp(new_cctype, "FILE") == 0) || (strcmp(new_cctype, "DIR") == 0)) ++ client->store.filename = xstrdup(new_ccname); ++#endif + + #ifdef USE_PAM + if (options.use_pam) +@@ -193,9 +208,76 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_cl + + krb5_cc_close(krb_context, ccache); + ++ client->store.data = krb_context; ++ + return; + } + ++int ++ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store, ++ ssh_gssapi_client *client) ++{ ++ krb5_ccache ccache = NULL; ++ krb5_principal principal = NULL; ++ char *name = NULL; ++ krb5_error_code problem; ++ OM_uint32 maj_status, min_status; ++ ++ if ((problem = krb5_cc_resolve(krb_context, store->envval, &ccache))) { ++ logit("krb5_cc_resolve(): %.100s", ++ krb5_get_err_text(krb_context, problem)); ++ return 0; ++ } ++ ++ /* Find out who the principal in this cache is */ ++ if ((problem = krb5_cc_get_principal(krb_context, ccache, ++ &principal))) { ++ logit("krb5_cc_get_principal(): %.100s", ++ krb5_get_err_text(krb_context, problem)); ++ krb5_cc_close(krb_context, ccache); ++ return 0; ++ } ++ ++ if ((problem = krb5_unparse_name(krb_context, principal, &name))) { ++ logit("krb5_unparse_name(): %.100s", ++ krb5_get_err_text(krb_context, problem)); ++ krb5_free_principal(krb_context, principal); ++ krb5_cc_close(krb_context, ccache); ++ return 0; ++ } ++ ++ ++ if (strcmp(name,client->exportedname.value)!=0) { ++ debug("Name in local credentials cache differs. Not storing"); ++ krb5_free_principal(krb_context, principal); ++ krb5_cc_close(krb_context, ccache); ++ krb5_free_unparsed_name(krb_context, name); ++ return 0; ++ } ++ krb5_free_unparsed_name(krb_context, name); ++ ++ /* Name matches, so lets get on with it! */ ++ ++ if ((problem = krb5_cc_initialize(krb_context, ccache, principal))) { ++ logit("krb5_cc_initialize(): %.100s", ++ krb5_get_err_text(krb_context, problem)); ++ krb5_free_principal(krb_context, principal); ++ krb5_cc_close(krb_context, ccache); ++ return 0; ++ } ++ ++ krb5_free_principal(krb_context, principal); ++ ++ if ((maj_status = gss_krb5_copy_ccache(&min_status, client->creds, ++ ccache))) { ++ logit("gss_krb5_copy_ccache() failed. Sorry!"); ++ krb5_cc_close(krb_context, ccache); ++ return 0; ++ } ++ ++ return 1; ++} ++ + ssh_gssapi_mech gssapi_kerberos_mech = { + "toWM5Slw5Ew8Mqkay+al2g==", + "Kerberos", +@@ -203,7 +285,8 @@ ssh_gssapi_mech gssapi_kerberos_mech = { + NULL, + &ssh_gssapi_krb5_userok, + NULL, +- &ssh_gssapi_krb5_storecreds ++ &ssh_gssapi_krb5_storecreds, ++ &ssh_gssapi_krb5_updatecreds + }; + + #endif /* KRB5 */ +diff --color -ruNp a/kex.c b/kex.c +--- a/kex.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/kex.c 2024-09-16 11:46:34.692939840 +0200 +@@ -297,17 +297,37 @@ static int + kex_compose_ext_info_server(struct ssh *ssh, struct sshbuf *m) + { + int r; ++ int have_key = 0; ++ int ext_count = 2; ++ ++#ifdef GSSAPI ++ /* ++ * Currently GSS KEX don't provide host keys as optional message, so ++ * no reasons to announce the publickey-hostbound extension ++ */ ++ if (ssh->kex->gss == NULL) ++ have_key = 1; ++#endif ++ ext_count += have_key; ++ + + if (ssh->kex->server_sig_algs == NULL && + (ssh->kex->server_sig_algs = sshkey_alg_list(0, 1, 1, ',')) == NULL) + return SSH_ERR_ALLOC_FAIL; +- if ((r = sshbuf_put_u32(m, 3)) != 0 || ++ if ((r = sshbuf_put_u32(m, ext_count)) != 0 || + (r = sshbuf_put_cstring(m, "server-sig-algs")) != 0 || +- (r = sshbuf_put_cstring(m, ssh->kex->server_sig_algs)) != 0 || +- (r = sshbuf_put_cstring(m, +- "publickey-hostbound@openssh.com")) != 0 || +- (r = sshbuf_put_cstring(m, "0")) != 0 || +- (r = sshbuf_put_cstring(m, "ping@openssh.com")) != 0 || ++ (r = sshbuf_put_cstring(m, ssh->kex->server_sig_algs)) != 0) { ++ error_fr(r, "compose"); ++ return r; ++ } ++ if (have_key) { ++ if ((r = sshbuf_put_cstring(m, "publickey-hostbound@openssh.com")) != 0 || ++ (r = sshbuf_put_cstring(m, "0")) != 0) { ++ error_fr(r, "compose"); ++ return r; ++ } ++ } ++ if ((r = sshbuf_put_cstring(m, "ping@openssh.com")) != 0 || + (r = sshbuf_put_cstring(m, "0")) != 0) { + error_fr(r, "compose"); + return r; +@@ -737,6 +757,9 @@ kex_free(struct kex *kex) + sshbuf_free(kex->server_version); + sshbuf_free(kex->client_pub); + sshbuf_free(kex->session_id); ++#ifdef GSSAPI ++ free(kex->gss_host); ++#endif /* GSSAPI */ + sshbuf_free(kex->initial_sig); + sshkey_free(kex->initial_hostkey); + free(kex->failed_choice); +diff --color -ruNp a/kexdh.c b/kexdh.c +--- a/kexdh.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/kexdh.c 2024-09-16 11:46:34.693939862 +0200 +@@ -49,13 +49,23 @@ kex_dh_keygen(struct kex *kex) + { + switch (kex->kex_type) { + case KEX_DH_GRP1_SHA1: ++#ifdef GSSAPI ++ case KEX_GSS_GRP1_SHA1: ++#endif + kex->dh = dh_new_group1(); + break; + case KEX_DH_GRP14_SHA1: + case KEX_DH_GRP14_SHA256: ++#ifdef GSSAPI ++ case KEX_GSS_GRP14_SHA1: ++ case KEX_GSS_GRP14_SHA256: ++#endif + kex->dh = dh_new_group14(); + break; + case KEX_DH_GRP16_SHA512: ++#ifdef GSSAPI ++ case KEX_GSS_GRP16_SHA512: ++#endif + kex->dh = dh_new_group16(); + break; + case KEX_DH_GRP18_SHA512: +diff --color -ruNp a/kexgen.c b/kexgen.c +--- a/kexgen.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/kexgen.c 2024-09-16 11:46:34.693939862 +0200 +@@ -44,7 +44,7 @@ + static int input_kex_gen_init(int, u_int32_t, struct ssh *); + static int input_kex_gen_reply(int type, u_int32_t seq, struct ssh *ssh); + +-static int ++int + kex_gen_hash( + int hash_alg, + const struct sshbuf *client_version, +diff --color -ruNp a/kexgssc.c b/kexgssc.c +--- a/kexgssc.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/kexgssc.c 2024-09-16 11:46:34.709940203 +0200 +@@ -0,0 +1,704 @@ ++/* ++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "includes.h" ++ ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ ++#include "includes.h" ++ ++#include ++#include ++ ++#include ++ ++#include "xmalloc.h" ++#include "sshbuf.h" ++#include "ssh2.h" ++#include "sshkey.h" ++#include "cipher.h" ++#include "kex.h" ++#include "log.h" ++#include "packet.h" ++#include "dh.h" ++#include "digest.h" ++#include "ssherr.h" ++ ++#include "ssh-gss.h" ++ ++static int input_kexgss_hostkey(int, u_int32_t, struct ssh *); ++static int input_kexgss_continue(int, u_int32_t, struct ssh *); ++static int input_kexgss_complete(int, u_int32_t, struct ssh *); ++static int input_kexgss_error(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_group(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_continue(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_complete(int, u_int32_t, struct ssh *); ++ ++static int ++kexgss_final(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ struct sshbuf *empty = NULL; ++ struct sshbuf *shared_secret = NULL; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ int r; ++ ++ /* ++ * We _must_ have received a COMPLETE message in reply from the ++ * server, which will have set server_blob and msg_tok ++ */ ++ ++ /* compute shared secret */ ++ switch (kex->kex_type) { ++ case KEX_GSS_GRP1_SHA1: ++ case KEX_GSS_GRP14_SHA1: ++ case KEX_GSS_GRP14_SHA256: ++ case KEX_GSS_GRP16_SHA512: ++ r = kex_dh_dec(kex, gss->server_blob, &shared_secret); ++ break; ++ case KEX_GSS_C25519_SHA256: ++ if (sshbuf_ptr(gss->server_blob)[sshbuf_len(gss->server_blob)] & 0x80) ++ fatal("The received key has MSB of last octet set!"); ++ r = kex_c25519_dec(kex, gss->server_blob, &shared_secret); ++ break; ++ case KEX_GSS_NISTP256_SHA256: ++ if (sshbuf_len(gss->server_blob) != 65) ++ fatal("The received NIST-P256 key did not match " ++ "expected length (expected 65, got %zu)", ++ sshbuf_len(gss->server_blob)); ++ ++ if (sshbuf_ptr(gss->server_blob)[0] != POINT_CONVERSION_UNCOMPRESSED) ++ fatal("The received NIST-P256 key does not have first octet 0x04"); ++ ++ r = kex_ecdh_dec(kex, gss->server_blob, &shared_secret); ++ break; ++ default: ++ r = SSH_ERR_INVALID_ARGUMENT; ++ break; ++ } ++ if (r != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ goto out; ++ } ++ ++ if ((empty = sshbuf_new()) == NULL) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ hashlen = sizeof(hash); ++ r = kex_gen_hash(kex->hash_alg, kex->client_version, ++ kex->server_version, kex->my, kex->peer, ++ (gss->server_host_key_blob ? gss->server_host_key_blob : empty), ++ kex->client_pub, gss->server_blob, shared_secret, ++ hash, &hashlen); ++ sshbuf_free(empty); ++ if (r != 0) ++ fatal_f("Unexpected KEX type %d", kex->kex_type); ++ ++ gss->buf.value = hash; ++ gss->buf.length = hashlen; ++ ++ /* Verify that the hash matches the MIC we just got. */ ++ if (GSS_ERROR(ssh_gssapi_checkmic(gss, &gss->buf, &gss->msg_tok))) ++ sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); ++ ++ gss_release_buffer(&gss->minor, &gss->msg_tok); ++ ++ if (kex->gss_deleg_creds) ++ ssh_gssapi_credentials_updated(gss); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = gss; ++ else ++ ssh_gssapi_delete_ctx(&kex->gss); ++ ++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) ++ r = kex_send_newkeys(ssh); ++ ++ if (kex->gss != NULL) { ++ sshbuf_free(gss->server_host_key_blob); ++ gss->server_host_key_blob = NULL; ++ sshbuf_free(gss->server_blob); ++ gss->server_blob = NULL; ++ } ++out: ++ explicit_bzero(kex->c25519_client_key, sizeof(kex->c25519_client_key)); ++ explicit_bzero(hash, sizeof(hash)); ++ sshbuf_free(shared_secret); ++ sshbuf_free(kex->client_pub); ++ kex->client_pub = NULL; ++ return r; ++} ++ ++static int ++kexgss_init_ctx(struct ssh *ssh, ++ gss_buffer_desc *token_ptr) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags; ++ int r; ++ ++ debug("Calling gss_init_sec_context"); ++ ++ gss->major = ssh_gssapi_init_ctx(gss, kex->gss_deleg_creds, ++ token_ptr, &send_tok, &ret_flags); ++ ++ if (GSS_ERROR(gss->major)) { ++ /* XXX Useless code: Missing send? */ ++ if (send_tok.length != 0) { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ fatal("gss_init_context failed"); ++ } ++ ++ /* If we've got an old receive buffer get rid of it */ ++ if (token_ptr != GSS_C_NO_BUFFER) ++ gss_release_buffer(&gss->minor, token_ptr); ++ ++ if (gss->major == GSS_S_COMPLETE) { ++ /* If mutual state flag is not true, kex fails */ ++ if (!(ret_flags & GSS_C_MUTUAL_FLAG)) ++ fatal("Mutual authentication failed"); ++ ++ /* If integ avail flag is not true kex fails */ ++ if (!(ret_flags & GSS_C_INTEG_FLAG)) ++ fatal("Integrity check failed"); ++ } ++ ++ /* ++ * If we have data to send, then the last message that we ++ * received cannot have been a 'complete'. ++ */ ++ if (send_tok.length != 0) { ++ if (gss->first) { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || ++ (r = sshpkt_put_stringb(ssh, kex->client_pub)) != 0) ++ fatal("failed to construct packet: %s", ssh_err(r)); ++ gss->first = 0; ++ } else { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) ++ fatal("failed to construct packet: %s", ssh_err(r)); ++ } ++ if ((r = sshpkt_send(ssh)) != 0) ++ fatal("failed to send packet: %s", ssh_err(r)); ++ gss_release_buffer(&gss->minor, &send_tok); ++ ++ /* If we've sent them data, they should reply */ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, &input_kexgss_hostkey); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgss_continue); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, &input_kexgss_complete); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, &input_kexgss_error); ++ return 0; ++ } ++ /* No data, and not complete */ ++ if (gss->major != GSS_S_COMPLETE) ++ fatal("Not complete, and no token output"); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return kexgss_init_ctx(ssh, token_ptr); ++ ++ return kexgss_final(ssh); ++} ++ ++int ++kexgss_client(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ int r; ++ ++ /* Initialise our GSSAPI world */ ++ ssh_gssapi_build_ctx(&kex->gss); ++ if (ssh_gssapi_id_kex(kex->gss, kex->name, kex->kex_type) == GSS_C_NO_OID) ++ fatal("Couldn't identify host exchange"); ++ ++ if (ssh_gssapi_import_name(kex->gss, kex->gss_host)) ++ fatal("Couldn't import hostname"); ++ ++ if (kex->gss_client && ++ ssh_gssapi_client_identity(kex->gss, kex->gss_client)) ++ fatal("Couldn't acquire client credentials"); ++ ++ /* Step 1 */ ++ switch (kex->kex_type) { ++ case KEX_GSS_GRP1_SHA1: ++ case KEX_GSS_GRP14_SHA1: ++ case KEX_GSS_GRP14_SHA256: ++ case KEX_GSS_GRP16_SHA512: ++ r = kex_dh_keypair(kex); ++ break; ++ case KEX_GSS_NISTP256_SHA256: ++ r = kex_ecdh_keypair(kex); ++ break; ++ case KEX_GSS_C25519_SHA256: ++ r = kex_c25519_keypair(kex); ++ break; ++ default: ++ fatal_f("Unexpected KEX type %d", kex->kex_type); ++ } ++ if (r != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ return r; ++ } ++ return kexgss_init_ctx(ssh, GSS_C_NO_BUFFER); ++} ++ ++static int ++input_kexgss_hostkey(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ u_char *tmp = NULL; ++ size_t tmp_len = 0; ++ int r; ++ ++ debug("Received KEXGSS_HOSTKEY"); ++ if (gss->server_host_key_blob) ++ fatal("Server host key received more than once"); ++ if ((r = sshpkt_get_string(ssh, &tmp, &tmp_len)) != 0) ++ fatal("Failed to read server host key: %s", ssh_err(r)); ++ if ((gss->server_host_key_blob = sshbuf_from(tmp, tmp_len)) == NULL) ++ fatal("sshbuf_from failed"); ++ return 0; ++} ++ ++static int ++input_kexgss_continue(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); ++ ++ debug("Received GSSAPI_CONTINUE"); ++ if (gss->major == GSS_S_COMPLETE) ++ fatal("GSSAPI Continue received from server when complete"); ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("Failed to read token: %s", ssh_err(r)); ++ if (!(gss->major & GSS_S_CONTINUE_NEEDED)) ++ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); ++ return kexgss_init_ctx(ssh, &recv_tok); ++} ++ ++static int ++input_kexgss_complete(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ u_char c; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); ++ ++ debug("Received GSSAPI_COMPLETE"); ++ if (gss->msg_tok.value != NULL) ++ fatal("Received GSSAPI_COMPLETE twice?"); ++ if ((r = sshpkt_getb_froms(ssh, &gss->server_blob)) != 0 || ++ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &gss->msg_tok)) != 0) ++ fatal("Failed to read message: %s", ssh_err(r)); ++ ++ /* Is there a token included? */ ++ if ((r = sshpkt_get_u8(ssh, &c)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ if (c) { ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0) ++ fatal("Failed to read token: %s", ssh_err(r)); ++ /* If we're already complete - protocol error */ ++ if (gss->major == GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: received token when complete"); ++ } else { ++ if (gss->major != GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); ++ } ++ if ((r = sshpkt_get_end(ssh)) != 0) ++ fatal("Expecting end of packet."); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return kexgss_init_ctx(ssh, &recv_tok); ++ ++ return kexgss_final(ssh); ++} ++ ++static int ++input_kexgss_error(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ u_char *msg; ++ int r; ++ ++ debug("Received Error"); ++ if ((r = sshpkt_get_u32(ssh, &gss->major)) != 0 || ++ (r = sshpkt_get_u32(ssh, &gss->minor)) != 0 || ++ (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 || ++ (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */ ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt_get failed: %s", ssh_err(r)); ++ fatal("GSSAPI Error: \n%.400s", msg); ++ return 0; ++} ++ ++/*******************************************************/ ++/******************** KEXGSSGEX ************************/ ++/*******************************************************/ ++ ++int ++kexgssgex_client(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ int r; ++ ++ /* Initialise our GSSAPI world */ ++ ssh_gssapi_build_ctx(&kex->gss); ++ if (ssh_gssapi_id_kex(kex->gss, kex->name, kex->kex_type) == GSS_C_NO_OID) ++ fatal("Couldn't identify host exchange"); ++ ++ if (ssh_gssapi_import_name(kex->gss, kex->gss_host)) ++ fatal("Couldn't import hostname"); ++ ++ if (kex->gss_client && ++ ssh_gssapi_client_identity(kex->gss, kex->gss_client)) ++ fatal("Couldn't acquire client credentials"); ++ ++ debug("Doing group exchange"); ++ kex->min = DH_GRP_MIN; ++ kex->max = DH_GRP_MAX; ++ kex->nbits = dh_estimate(kex->dh_need * 8); ++ ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUPREQ)) != 0 || ++ (r = sshpkt_put_u32(ssh, kex->min)) != 0 || ++ (r = sshpkt_put_u32(ssh, kex->nbits)) != 0 || ++ (r = sshpkt_put_u32(ssh, kex->max)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("Failed to construct a packet: %s", ssh_err(r)); ++ ++ debug("Wait SSH2_MSG_KEXGSS_GROUP"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUP, &input_kexgssgex_group); ++ return 0; ++} ++ ++static int ++kexgssgex_final(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ struct sshbuf *buf = NULL; ++ struct sshbuf *empty = NULL; ++ struct sshbuf *shared_secret = NULL; ++ BIGNUM *dh_server_pub = NULL; ++ const BIGNUM *pub_key, *dh_p, *dh_g; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ int r = SSH_ERR_INTERNAL_ERROR; ++ ++ /* ++ * We _must_ have received a COMPLETE message in reply from the ++ * server, which will have set server_blob and msg_tok ++ */ ++ ++ /* 7. C verifies that the key Q_S is valid */ ++ /* 8. C computes shared secret */ ++ if ((buf = sshbuf_new()) == NULL || ++ (r = sshbuf_put_stringb(buf, gss->server_blob)) != 0 || ++ (r = sshbuf_get_bignum2(buf, &dh_server_pub)) != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ goto out; ++ } ++ sshbuf_free(buf); ++ buf = NULL; ++ ++ if ((shared_secret = sshbuf_new()) == NULL) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ goto out; ++ } ++ ++ if ((empty = sshbuf_new()) == NULL) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ DH_get0_key(kex->dh, &pub_key, NULL); ++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); ++ hashlen = sizeof(hash); ++ r = kexgex_hash(kex->hash_alg, kex->client_version, ++ kex->server_version, kex->my, kex->peer, ++ (gss->server_host_key_blob ? gss->server_host_key_blob : empty), ++ kex->min, kex->nbits, kex->max, dh_p, dh_g, pub_key, ++ dh_server_pub, sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), ++ hash, &hashlen); ++ sshbuf_free(empty); ++ if (r != 0) ++ fatal("Failed to calculate hash: %s", ssh_err(r)); ++ ++ gss->buf.value = hash; ++ gss->buf.length = hashlen; ++ ++ /* Verify that the hash matches the MIC we just got. */ ++ if (GSS_ERROR(ssh_gssapi_checkmic(gss, &gss->buf, &gss->msg_tok))) ++ sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); ++ ++ gss_release_buffer(&gss->minor, &gss->msg_tok); ++ ++ if (kex->gss_deleg_creds) ++ ssh_gssapi_credentials_updated(gss); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = gss; ++ else ++ ssh_gssapi_delete_ctx(&kex->gss); ++ ++ /* Finally derive the keys and send them */ ++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) ++ r = kex_send_newkeys(ssh); ++ ++ if (kex->gss != NULL) { ++ sshbuf_free(gss->server_host_key_blob); ++ gss->server_host_key_blob = NULL; ++ sshbuf_free(gss->server_blob); ++ gss->server_blob = NULL; ++ } ++out: ++ explicit_bzero(hash, sizeof(hash)); ++ DH_free(kex->dh); ++ kex->dh = NULL; ++ BN_clear_free(dh_server_pub); ++ sshbuf_free(shared_secret); ++ return r; ++} ++ ++static int ++kexgssgex_init_ctx(struct ssh *ssh, ++ gss_buffer_desc *token_ptr) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ const BIGNUM *pub_key; ++ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags; ++ int r; ++ ++ /* Step 2 - call GSS_Init_sec_context() */ ++ debug("Calling gss_init_sec_context"); ++ ++ gss->major = ssh_gssapi_init_ctx(gss, kex->gss_deleg_creds, ++ token_ptr, &send_tok, &ret_flags); ++ ++ if (GSS_ERROR(gss->major)) { ++ /* XXX Useless code: Missing send? */ ++ if (send_tok.length != 0) { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ fatal("gss_init_context failed"); ++ } ++ ++ /* If we've got an old receive buffer get rid of it */ ++ if (token_ptr != GSS_C_NO_BUFFER) ++ gss_release_buffer(&gss->minor, token_ptr); ++ ++ if (gss->major == GSS_S_COMPLETE) { ++ /* If mutual state flag is not true, kex fails */ ++ if (!(ret_flags & GSS_C_MUTUAL_FLAG)) ++ fatal("Mutual authentication failed"); ++ ++ /* If integ avail flag is not true kex fails */ ++ if (!(ret_flags & GSS_C_INTEG_FLAG)) ++ fatal("Integrity check failed"); ++ } ++ ++ /* ++ * If we have data to send, then the last message that we ++ * received cannot have been a 'complete'. ++ */ ++ if (send_tok.length != 0) { ++ if (gss->first) { ++ DH_get0_key(kex->dh, &pub_key, NULL); ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || ++ (r = sshpkt_put_bignum2(ssh, pub_key)) != 0) ++ fatal("failed to construct packet: %s", ssh_err(r)); ++ gss->first = 0; ++ } else { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) ++ fatal("failed to construct packet: %s", ssh_err(r)); ++ } ++ if ((r = sshpkt_send(ssh)) != 0) ++ fatal("failed to send packet: %s", ssh_err(r)); ++ gss_release_buffer(&gss->minor, &send_tok); ++ ++ /* If we've sent them data, they should reply */ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, &input_kexgss_hostkey); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgssgex_continue); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, &input_kexgssgex_complete); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, &input_kexgss_error); ++ return 0; ++ } ++ /* No data, and not complete */ ++ if (gss->major != GSS_S_COMPLETE) ++ fatal("Not complete, and no token output"); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return kexgssgex_init_ctx(ssh, token_ptr); ++ ++ return kexgssgex_final(ssh); ++} ++ ++static int ++input_kexgssgex_group(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ BIGNUM *p = NULL; ++ BIGNUM *g = NULL; ++ int r; ++ ++ debug("Received SSH2_MSG_KEXGSS_GROUP"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUP, NULL); ++ ++ if ((r = sshpkt_get_bignum2(ssh, &p)) != 0 || ++ (r = sshpkt_get_bignum2(ssh, &g)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("shpkt_get_bignum2 failed: %s", ssh_err(r)); ++ ++ if (BN_num_bits(p) < kex->min || BN_num_bits(p) > kex->max) ++ fatal("GSSGRP_GEX group out of range: %d !< %d !< %d", ++ kex->min, BN_num_bits(p), kex->max); ++ ++ if ((kex->dh = dh_new_group(g, p)) == NULL) ++ fatal("dn_new_group() failed"); ++ p = g = NULL; /* belong to kex->dh now */ ++ ++ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ DH_free(kex->dh); ++ kex->dh = NULL; ++ return r; ++ } ++ ++ return kexgssgex_init_ctx(ssh, GSS_C_NO_BUFFER); ++} ++ ++static int ++input_kexgssgex_continue(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); ++ ++ debug("Received GSSAPI_CONTINUE"); ++ if (gss->major == GSS_S_COMPLETE) ++ fatal("GSSAPI Continue received from server when complete"); ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("Failed to read token: %s", ssh_err(r)); ++ if (!(gss->major & GSS_S_CONTINUE_NEEDED)) ++ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); ++ return kexgssgex_init_ctx(ssh, &recv_tok); ++} ++ ++static int ++input_kexgssgex_complete(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; ++ u_char c; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); ++ ++ debug("Received GSSAPI_COMPLETE"); ++ if (gss->msg_tok.value != NULL) ++ fatal("Received GSSAPI_COMPLETE twice?"); ++ if ((r = sshpkt_getb_froms(ssh, &gss->server_blob)) != 0 || ++ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &gss->msg_tok)) != 0) ++ fatal("Failed to read message: %s", ssh_err(r)); ++ ++ /* Is there a token included? */ ++ if ((r = sshpkt_get_u8(ssh, &c)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ if (c) { ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0) ++ fatal("Failed to read token: %s", ssh_err(r)); ++ /* If we're already complete - protocol error */ ++ if (gss->major == GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: received token when complete"); ++ } else { ++ if (gss->major != GSS_S_COMPLETE) ++ sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); ++ } ++ if ((r = sshpkt_get_end(ssh)) != 0) ++ fatal("Expecting end of packet."); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return kexgssgex_init_ctx(ssh, &recv_tok); ++ ++ return kexgssgex_final(ssh); ++} ++ ++#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ +diff --color -ruNp a/kexgsss.c b/kexgsss.c +--- a/kexgsss.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/kexgsss.c 2024-09-16 11:46:34.710940224 +0200 +@@ -0,0 +1,590 @@ ++/* ++ * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "includes.h" ++ ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ ++#include ++ ++#include ++#include ++ ++#include "xmalloc.h" ++#include "sshbuf.h" ++#include "ssh2.h" ++#include "sshkey.h" ++#include "cipher.h" ++#include "kex.h" ++#include "log.h" ++#include "packet.h" ++#include "dh.h" ++#include "ssh-gss.h" ++#include "monitor_wrap.h" ++#include "misc.h" /* servconf.h needs misc.h for struct ForwardOptions */ ++#include "servconf.h" ++#include "ssh-gss.h" ++#include "digest.h" ++#include "ssherr.h" ++ ++extern ServerOptions options; ++ ++static int input_kexgss_init(int, u_int32_t, struct ssh *); ++static int input_kexgss_continue(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_groupreq(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_init(int, u_int32_t, struct ssh *); ++static int input_kexgssgex_continue(int, u_int32_t, struct ssh *); ++ ++int ++kexgss_server(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ gss_OID oid; ++ char *mechs; ++ ++ /* Initialise GSSAPI */ ++ ++ /* If we're rekeying, privsep means that some of the private structures ++ * in the GSSAPI code are no longer available. This kludges them back ++ * into life ++ */ ++ if (!ssh_gssapi_oid_table_ok()) { ++ mechs = ssh_gssapi_server_mechanisms(); ++ free(mechs); ++ } ++ ++ debug2_f("Identifying %s", kex->name); ++ oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type); ++ if (oid == GSS_C_NO_OID) ++ fatal("Unknown gssapi mechanism"); ++ ++ debug2_f("Acquiring credentials"); ++ ++ if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, oid))) ++ fatal("Unable to acquire credentials for the server"); ++ ++ ssh_gssapi_build_ctx(&kex->gss); ++ if (kex->gss == NULL) ++ fatal("Unable to allocate memory for gss context"); ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, &input_kexgss_init); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgss_continue); ++ debug("Wait SSH2_MSG_KEXGSS_INIT"); ++ return 0; ++} ++ ++static inline void ++kexgss_accept_ctx(struct ssh *ssh, ++ gss_buffer_desc *recv_tok, ++ gss_buffer_desc *send_tok, ++ OM_uint32 *ret_flags) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ int r; ++ ++ gss->major = mm_ssh_gssapi_accept_ctx(gss, recv_tok, send_tok, ret_flags); ++ gss_release_buffer(&gss->minor, recv_tok); ++ ++ if (gss->major != GSS_S_COMPLETE && send_tok->length == 0) ++ fatal("Zero length token output when incomplete"); ++ ++ if (gss->buf.value == NULL) ++ fatal("No client public key"); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) { ++ debug("Sending GSSAPI_CONTINUE"); ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ gss_release_buffer(&gss->minor, send_tok); ++ } ++} ++ ++static inline int ++kexgss_final(struct ssh *ssh, ++ gss_buffer_desc *send_tok, ++ OM_uint32 *ret_flags) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ gss_buffer_desc msg_tok; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ++ if (GSS_ERROR(gss->major)) { ++ if (send_tok->length > 0) { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ fatal("accept_ctx died"); ++ } ++ ++ if (!(*ret_flags & GSS_C_MUTUAL_FLAG)) ++ fatal("Mutual Authentication flag wasn't set"); ++ ++ if (!(*ret_flags & GSS_C_INTEG_FLAG)) ++ fatal("Integrity flag wasn't set"); ++ ++ if (GSS_ERROR(mm_ssh_gssapi_sign(gss, &gss->buf, &msg_tok))) ++ fatal("Couldn't get MIC"); ++ ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 || ++ (r = sshpkt_put_stringb(ssh, gss->server_pubkey)) != 0 || ++ (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ if (send_tok->length != 0) { ++ if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */ ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } else { ++ if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */ ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ if ((r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt_send failed: %s", ssh_err(r)); ++ ++ gss_release_buffer(&gss->minor, send_tok); ++ gss_release_buffer(&gss->minor, &msg_tok); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = gss; ++ else ++ ssh_gssapi_delete_ctx(&kex->gss); ++ ++ if ((r = kex_derive_keys(ssh, gss->hash, gss->hashlen, gss->shared_secret)) == 0) ++ r = kex_send_newkeys(ssh); ++ ++ /* If this was a rekey, then save out any delegated credentials we ++ * just exchanged. */ ++ if (options.gss_store_rekey) ++ ssh_gssapi_rekey_creds(); ++ ++ if (kex->gss != NULL) { ++ explicit_bzero(gss->hash, sizeof(gss->hash)); ++ sshbuf_free(gss->shared_secret); ++ gss->shared_secret = NULL; ++ sshbuf_free(gss->server_pubkey); ++ gss->server_pubkey = NULL; ++ } ++ return r; ++} ++ ++static int ++input_kexgss_init(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ struct sshbuf *empty; ++ struct sshbuf *client_pubkey = NULL; ++ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags = 0; ++ int r; ++ ++ debug("SSH2_MSG_KEXGSS_INIT received"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); ++ ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_getb_froms(ssh, &client_pubkey)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ switch (kex->kex_type) { ++ case KEX_GSS_GRP1_SHA1: ++ case KEX_GSS_GRP14_SHA1: ++ case KEX_GSS_GRP14_SHA256: ++ case KEX_GSS_GRP16_SHA512: ++ r = kex_dh_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret); ++ break; ++ case KEX_GSS_NISTP256_SHA256: ++ r = kex_ecdh_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret); ++ break; ++ case KEX_GSS_C25519_SHA256: ++ r = kex_c25519_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret); ++ break; ++ default: ++ fatal_f("Unexpected KEX type %d", kex->kex_type); ++ } ++ if (r != 0) { ++ sshbuf_free(client_pubkey); ++ ssh_gssapi_delete_ctx(&kex->gss); ++ return r; ++ } ++ ++ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ ++ ++ if ((empty = sshbuf_new()) == NULL) { ++ sshbuf_free(client_pubkey); ++ ssh_gssapi_delete_ctx(&kex->gss); ++ return SSH_ERR_ALLOC_FAIL; ++ } ++ ++ /* Calculate the hash early so we can free the ++ * client_pubkey, which has reference to the parent ++ * buffer state->incoming_packet ++ */ ++ gss->hashlen = sizeof(gss->hash); ++ r = kex_gen_hash(kex->hash_alg, kex->client_version, kex->server_version, ++ kex->peer, kex->my, empty, client_pubkey, gss->server_pubkey, ++ gss->shared_secret, gss->hash, &gss->hashlen); ++ sshbuf_free(empty); ++ sshbuf_free(client_pubkey); ++ if (r != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ return r; ++ } ++ ++ gss->buf.value = gss->hash; ++ gss->buf.length = gss->hashlen; ++ ++ kexgss_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return 0; ++ ++ return kexgss_final(ssh, &send_tok, &ret_flags); ++} ++ ++static int ++input_kexgss_continue(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags = 0; ++ int r; ++ ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ kexgss_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return 0; ++ ++ return kexgss_final(ssh, &send_tok, &ret_flags); ++} ++ ++/*******************************************************/ ++/******************** KEXGSSGEX ************************/ ++/*******************************************************/ ++ ++int ++kexgssgex_server(struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ gss_OID oid; ++ char *mechs; ++ ++ /* Initialise GSSAPI */ ++ ++ /* If we're rekeying, privsep means that some of the private structures ++ * in the GSSAPI code are no longer available. This kludges them back ++ * into life ++ */ ++ if (!ssh_gssapi_oid_table_ok()) { ++ mechs = ssh_gssapi_server_mechanisms(); ++ free(mechs); ++ } ++ ++ debug2_f("Identifying %s", kex->name); ++ oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type); ++ if (oid == GSS_C_NO_OID) ++ fatal("Unknown gssapi mechanism"); ++ ++ debug2_f("Acquiring credentials"); ++ ++ if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, oid))) ++ fatal("Unable to acquire credentials for the server"); ++ ++ ssh_gssapi_build_ctx(&kex->gss); ++ if (kex->gss == NULL) ++ fatal("Unable to allocate memory for gss context"); ++ ++ debug("Doing group exchange"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUPREQ, &input_kexgssgex_groupreq); ++ return 0; ++} ++ ++static inline void ++kexgssgex_accept_ctx(struct ssh *ssh, ++ gss_buffer_desc *recv_tok, ++ gss_buffer_desc *send_tok, ++ OM_uint32 *ret_flags) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ int r; ++ ++ gss->major = mm_ssh_gssapi_accept_ctx(gss, recv_tok, send_tok, ret_flags); ++ gss_release_buffer(&gss->minor, recv_tok); ++ ++ if (gss->major != GSS_S_COMPLETE && send_tok->length == 0) ++ fatal("Zero length token output when incomplete"); ++ ++ if (gss->dh_client_pub == NULL) ++ fatal("No client public key"); ++ ++ if (gss->major & GSS_S_CONTINUE_NEEDED) { ++ debug("Sending GSSAPI_CONTINUE"); ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ gss_release_buffer(&gss->minor, send_tok); ++ } ++} ++ ++static inline int ++kexgssgex_final(struct ssh *ssh, ++ gss_buffer_desc *send_tok, ++ OM_uint32 *ret_flags) ++{ ++ struct kex *kex = ssh->kex; ++ Gssctxt *gss = kex->gss; ++ gss_buffer_desc msg_tok; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t hashlen; ++ const BIGNUM *pub_key, *dh_p, *dh_g; ++ struct sshbuf *shared_secret = NULL; ++ struct sshbuf *empty = NULL; ++ int r; ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); ++ ++ if (GSS_ERROR(gss->major)) { ++ if (send_tok->length > 0) { ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ fatal("accept_ctx died"); ++ } ++ ++ if (!(*ret_flags & GSS_C_MUTUAL_FLAG)) ++ fatal("Mutual Authentication flag wasn't set"); ++ ++ if (!(*ret_flags & GSS_C_INTEG_FLAG)) ++ fatal("Integrity flag wasn't set"); ++ ++ /* calculate shared secret */ ++ shared_secret = sshbuf_new(); ++ if (shared_secret == NULL) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ if ((r = kex_dh_compute_key(kex, gss->dh_client_pub, shared_secret)) != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ goto out; ++ } ++ ++ if ((empty = sshbuf_new()) == NULL) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ ++ DH_get0_key(kex->dh, &pub_key, NULL); ++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); ++ hashlen = sizeof(hash); ++ r = kexgex_hash(kex->hash_alg, kex->client_version, kex->server_version, ++ kex->peer, kex->my, empty, kex->min, kex->nbits, kex->max, dh_p, dh_g, ++ gss->dh_client_pub, pub_key, sshbuf_ptr(shared_secret), ++ sshbuf_len(shared_secret), hash, &hashlen); ++ sshbuf_free(empty); ++ if (r != 0) ++ fatal("kexgex_hash failed: %s", ssh_err(r)); ++ ++ gss->buf.value = hash; ++ gss->buf.length = hashlen; ++ ++ if (GSS_ERROR(mm_ssh_gssapi_sign(gss, &gss->buf, &msg_tok))) ++ fatal("Couldn't get MIC"); ++ ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 || ++ (r = sshpkt_put_bignum2(ssh, pub_key)) != 0 || ++ (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ if (send_tok->length != 0) { ++ if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */ ++ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } else { ++ if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */ ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ } ++ if ((r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt_send failed: %s", ssh_err(r)); ++ ++ gss_release_buffer(&gss->minor, send_tok); ++ gss_release_buffer(&gss->minor, &msg_tok); ++ ++ if (gss_kex_context == NULL) ++ gss_kex_context = gss; ++ else ++ ssh_gssapi_delete_ctx(&kex->gss); ++ ++ /* Finally derive the keys and send them */ ++ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) ++ r = kex_send_newkeys(ssh); ++ ++ /* If this was a rekey, then save out any delegated credentials we ++ * just exchanged. */ ++ if (options.gss_store_rekey) ++ ssh_gssapi_rekey_creds(); ++ ++ if (kex->gss != NULL) ++ BN_clear_free(gss->dh_client_pub); ++ ++out: ++ explicit_bzero(hash, sizeof(hash)); ++ DH_free(kex->dh); ++ kex->dh = NULL; ++ sshbuf_free(shared_secret); ++ return r; ++} ++ ++static int ++input_kexgssgex_groupreq(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ struct kex *kex = ssh->kex; ++ const BIGNUM *dh_p, *dh_g; ++ int min = -1, max = -1, nbits = -1; ++ int cmin = -1, cmax = -1; /* client proposal */ ++ int r; ++ ++ /* 5. S generates an ephemeral key pair (do the allocations early) */ ++ ++ debug("SSH2_MSG_KEXGSS_GROUPREQ received"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUPREQ, NULL); ++ ++ /* store client proposal to provide valid signature */ ++ if ((r = sshpkt_get_u32(ssh, &cmin)) != 0 || ++ (r = sshpkt_get_u32(ssh, &nbits)) != 0 || ++ (r = sshpkt_get_u32(ssh, &cmax)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ kex->nbits = nbits; ++ kex->min = cmin; ++ kex->max = cmax; ++ min = MAX(DH_GRP_MIN, cmin); ++ max = MIN(DH_GRP_MAX, cmax); ++ nbits = MAXIMUM(DH_GRP_MIN, nbits); ++ nbits = MINIMUM(DH_GRP_MAX, nbits); ++ ++ if (max < min || nbits < min || max < nbits) ++ fatal("GSS_GEX, bad parameters: %d !< %d !< %d", min, nbits, max); ++ ++ kex->dh = mm_choose_dh(min, nbits, max); ++ if (kex->dh == NULL) { ++ sshpkt_disconnect(ssh, "Protocol error: no matching group found"); ++ fatal("Protocol error: no matching group found"); ++ } ++ ++ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); ++ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 || ++ (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 || ++ (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ if ((r = ssh_packet_write_wait(ssh)) != 0) ++ fatal("ssh_packet_write_wait: %s", ssh_err(r)); ++ ++ /* Compute our exchange value in parallel with the client */ ++ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) { ++ ssh_gssapi_delete_ctx(&kex->gss); ++ DH_free(kex->dh); ++ kex->dh = NULL; ++ return r; ++ } ++ ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, &input_kexgssgex_init); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgssgex_continue); ++ debug("Wait SSH2_MSG_KEXGSS_INIT"); ++ return 0; ++} ++ ++static int ++input_kexgssgex_init(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags = 0; ++ int r; ++ ++ debug("SSH2_MSG_KEXGSS_INIT received"); ++ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); ++ ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_get_bignum2(ssh, &gss->dh_client_pub)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ ++ ++ kexgssgex_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return 0; ++ ++ return kexgssgex_final(ssh, &send_tok, &ret_flags); ++} ++ ++static int ++input_kexgssgex_continue(int type, ++ u_int32_t seq, ++ struct ssh *ssh) ++{ ++ Gssctxt *gss = ssh->kex->gss; ++ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ret_flags = 0; ++ int r; ++ ++ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || ++ (r = sshpkt_get_end(ssh)) != 0) ++ fatal("sshpkt failed: %s", ssh_err(r)); ++ ++ kexgssgex_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); ++ if (gss->major & GSS_S_CONTINUE_NEEDED) ++ return 0; ++ ++ return kexgssgex_final(ssh, &send_tok, &ret_flags); ++} ++ ++#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ +diff --color -ruNp a/kex.h b/kex.h +--- a/kex.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/kex.h 2024-09-16 11:46:34.710940224 +0200 +@@ -29,6 +29,10 @@ + #include "mac.h" + #include "crypto_api.h" + ++#ifdef GSSAPI ++# include "ssh-gss.h" /* Gssctxt */ ++#endif ++ + #ifdef WITH_OPENSSL + # include + # include +@@ -102,6 +106,15 @@ enum kex_exchange { + KEX_C25519_SHA256, + KEX_KEM_SNTRUP761X25519_SHA512, + KEX_KEM_MLKEM768X25519_SHA256, ++#ifdef GSSAPI ++ KEX_GSS_GRP1_SHA1, ++ KEX_GSS_GRP14_SHA1, ++ KEX_GSS_GRP14_SHA256, ++ KEX_GSS_GRP16_SHA512, ++ KEX_GSS_GEX_SHA1, ++ KEX_GSS_NISTP256_SHA256, ++ KEX_GSS_C25519_SHA256, ++#endif + KEX_MAX + }; + +@@ -164,6 +177,13 @@ struct kex { + u_int flags; + int hash_alg; + int ec_nid; ++#ifdef GSSAPI ++ Gssctxt *gss; ++ int gss_deleg_creds; ++ int gss_trust_dns; ++ char *gss_host; ++ char *gss_client; ++#endif + char *failed_choice; + int (*verify_host_key)(struct sshkey *, struct ssh *); + struct sshkey *(*load_host_public_key)(int, int, struct ssh *); +@@ -189,8 +209,10 @@ int kex_hash_from_name(const char *); + int kex_nid_from_name(const char *); + int kex_names_valid(const char *); + char *kex_alg_list(char); ++char *kex_gss_alg_list(char); + char *kex_names_cat(const char *, const char *); + int kex_has_any_alg(const char *, const char *); ++int kex_gss_names_valid(const char *); + int kex_assemble_names(char **, const char *, const char *); + void kex_proposal_populate_entries(struct ssh *, char *prop[PROPOSAL_MAX], + const char *, const char *, const char *, const char *, const char *); +@@ -224,6 +246,12 @@ int kexgex_client(struct ssh *); + int kexgex_server(struct ssh *); + int kex_gen_client(struct ssh *); + int kex_gen_server(struct ssh *); ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++int kexgssgex_client(struct ssh *); ++int kexgssgex_server(struct ssh *); ++int kexgss_client(struct ssh *); ++int kexgss_server(struct ssh *); ++#endif + + int kex_dh_keypair(struct kex *); + int kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **, +@@ -256,6 +284,12 @@ int kexgex_hash(int, const struct sshbu + const BIGNUM *, const u_char *, size_t, + u_char *, size_t *); + ++int kex_gen_hash(int hash_alg, const struct sshbuf *client_version, ++ const struct sshbuf *server_version, const struct sshbuf *client_kexinit, ++ const struct sshbuf *server_kexinit, const struct sshbuf *server_host_key_blob, ++ const struct sshbuf *client_pub, const struct sshbuf *server_pub, ++ const struct sshbuf *shared_secret, u_char *hash, size_t *hashlen); ++ + void kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE]) + __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) + __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); +diff --color -ruNp a/kex-names.c b/kex-names.c +--- a/kex-names.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/kex-names.c 2024-09-16 11:46:34.694939883 +0200 +@@ -45,6 +45,10 @@ + #include "ssherr.h" + #include "xmalloc.h" + ++#ifdef GSSAPI ++#include "ssh-gss.h" ++#endif ++ + struct kexalg { + char *name; + u_int type; +@@ -83,15 +87,28 @@ static const struct kexalg kexalgs[] = { + #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */ + { NULL, 0, -1, -1}, + }; ++static const struct kexalg gss_kexalgs[] = { ++#ifdef GSSAPI ++ { KEX_GSS_GEX_SHA1_ID, KEX_GSS_GEX_SHA1, 0, SSH_DIGEST_SHA1 }, ++ { KEX_GSS_GRP1_SHA1_ID, KEX_GSS_GRP1_SHA1, 0, SSH_DIGEST_SHA1 }, ++ { KEX_GSS_GRP14_SHA1_ID, KEX_GSS_GRP14_SHA1, 0, SSH_DIGEST_SHA1 }, ++ { KEX_GSS_GRP14_SHA256_ID, KEX_GSS_GRP14_SHA256, 0, SSH_DIGEST_SHA256 }, ++ { KEX_GSS_GRP16_SHA512_ID, KEX_GSS_GRP16_SHA512, 0, SSH_DIGEST_SHA512 }, ++ { KEX_GSS_NISTP256_SHA256_ID, KEX_GSS_NISTP256_SHA256, ++ NID_X9_62_prime256v1, SSH_DIGEST_SHA256 }, ++ { KEX_GSS_C25519_SHA256_ID, KEX_GSS_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, ++#endif ++ { NULL, 0, -1, -1}, ++}; + +-char * +-kex_alg_list(char sep) ++static char * ++kex_alg_list_internal(char sep, const struct kexalg *algs) + { + char *ret = NULL, *tmp; + size_t nlen, rlen = 0; + const struct kexalg *k; + +- for (k = kexalgs; k->name != NULL; k++) { ++ for (k = algs; k->name != NULL; k++) { + if (ret != NULL) + ret[rlen++] = sep; + nlen = strlen(k->name); +@@ -106,6 +123,18 @@ kex_alg_list(char sep) + return ret; + } + ++char * ++kex_alg_list(char sep) ++{ ++ return kex_alg_list_internal(sep, kexalgs); ++} ++ ++char * ++kex_gss_alg_list(char sep) ++{ ++ return kex_alg_list_internal(sep, gss_kexalgs); ++} ++ + static const struct kexalg * + kex_alg_by_name(const char *name) + { +@@ -115,6 +144,10 @@ kex_alg_by_name(const char *name) + if (strcmp(k->name, name) == 0) + return k; + } ++ for (k = gss_kexalgs; k->name != NULL; k++) { ++ if (strncmp(k->name, name, strlen(k->name)) == 0) ++ return k; ++ } + return NULL; + } + +@@ -328,3 +361,26 @@ kex_assemble_names(char **listp, const c + free(ret); + return r; + } ++ ++/* Validate GSS KEX method name list */ ++int ++kex_gss_names_valid(const char *names) ++{ ++ char *s, *cp, *p; ++ ++ if (names == NULL || *names == '\0') ++ return 0; ++ s = cp = xstrdup(names); ++ for ((p = strsep(&cp, ",")); p && *p != '\0'; ++ (p = strsep(&cp, ","))) { ++ if (strncmp(p, "gss-", 4) != 0 ++ || kex_alg_by_name(p) == NULL) { ++ error("Unsupported KEX algorithm \"%.100s\"", p); ++ free(s); ++ return 0; ++ } ++ } ++ debug3("gss kex names ok: [%s]", names); ++ free(s); ++ return 1; ++} +diff --color -ruNp a/Makefile.in b/Makefile.in +--- a/Makefile.in 2024-09-16 11:45:56.868133454 +0200 ++++ b/Makefile.in 2024-09-16 11:46:34.695939904 +0200 +@@ -114,6 +114,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ + kex.o kex-names.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ + kexgexc.o kexgexs.o \ + kexsntrup761x25519.o kexmlkem768x25519.o sntrup761.o kexgen.o \ ++ kexgssc.o \ + sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ + sshbuf-io.o + +@@ -135,7 +136,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rh + auth2-chall.o groupaccess.o \ + auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ + auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \ +- monitor.o monitor_wrap.o auth-krb5.o \ ++ monitor.o monitor_wrap.o auth-krb5.o kexgsss.o \ + auth2-gss.o gss-serv.o gss-serv-krb5.o \ + loginrec.o auth-pam.o auth-shadow.o auth-sia.o \ + sftp-server.o sftp-common.o \ +@@ -529,7 +530,7 @@ regress-prep: + ln -s `cd $(srcdir) && pwd`/regress/Makefile `pwd`/regress/Makefile + + REGRESSLIBS=libssh.a $(LIBCOMPAT) +-TESTLIBS=$(LIBS) $(CHANNELLIBS) ++TESTLIBS=$(LIBS) $(CHANNELLIBS) $(GSSLIBS) + + regress/modpipe$(EXEEXT): $(srcdir)/regress/modpipe.c $(REGRESSLIBS) + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(srcdir)/regress/modpipe.c \ +diff --color -ruNp a/monitor.c b/monitor.c +--- a/monitor.c 2024-09-16 11:45:56.861133305 +0200 ++++ b/monitor.c 2024-09-16 11:46:34.696939926 +0200 +@@ -143,6 +143,8 @@ int mm_answer_gss_setup_ctx(struct ssh * + int mm_answer_gss_accept_ctx(struct ssh *, int, struct sshbuf *); + int mm_answer_gss_userok(struct ssh *, int, struct sshbuf *); + int mm_answer_gss_checkmic(struct ssh *, int, struct sshbuf *); ++int mm_answer_gss_sign(struct ssh *, int, struct sshbuf *); ++int mm_answer_gss_updatecreds(struct ssh *, int, struct sshbuf *); + #endif + + #ifdef SSH_AUDIT_EVENTS +@@ -219,11 +221,18 @@ struct mon_table mon_dispatch_proto20[] + {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, + {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok}, + {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic}, ++ {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign}, + #endif + {0, 0, NULL} + }; + + struct mon_table mon_dispatch_postauth20[] = { ++#ifdef GSSAPI ++ {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx}, ++ {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, ++ {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign}, ++ {MONITOR_REQ_GSSUPCREDS, 0, mm_answer_gss_updatecreds}, ++#endif + #ifdef WITH_OPENSSL + {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, + #endif +@@ -292,6 +301,10 @@ monitor_child_preauth(struct ssh *ssh, s + /* Permit requests for moduli and signatures */ + monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); ++#ifdef GSSAPI ++ /* and for the GSSAPI key exchange */ ++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); ++#endif + + /* The first few requests do not require asynchronous access */ + while (!authenticated) { +@@ -344,8 +357,15 @@ monitor_child_preauth(struct ssh *ssh, s + if (ent->flags & (MON_AUTHDECIDE|MON_ALOG)) { + auth_log(ssh, authenticated, partial, + auth_method, auth_submethod); +- if (!partial && !authenticated) ++ if (!partial && !authenticated) { ++#ifdef GSSAPI ++ /* If gssapi-with-mic failed, MONITOR_REQ_GSSCHECKMIC is disabled. ++ * We have to reenable it to try again for gssapi-keyex */ ++ if (strcmp(auth_method, "gssapi-with-mic") == 0 && options.gss_keyex) ++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); ++#endif + authctxt->failures++; ++ } + if (authenticated || partial) { + auth2_update_session_info(authctxt, + auth_method, auth_submethod); +@@ -413,6 +433,10 @@ monitor_child_postauth(struct ssh *ssh, + monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); ++#ifdef GSSAPI ++ /* and for the GSSAPI key exchange */ ++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); ++#endif + + if (auth_opts->permit_pty_flag) { + monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1); +@@ -1793,6 +1817,17 @@ monitor_apply_keystate(struct ssh *ssh, + # ifdef OPENSSL_HAS_ECC + kex->kex[KEX_ECDH_SHA2] = kex_gen_server; + # endif ++# ifdef GSSAPI ++ if (options.gss_keyex) { ++ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; ++ kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; ++ kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server; ++ kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server; ++ kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server; ++ kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server; ++ kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server; ++ } ++# endif + #endif /* WITH_OPENSSL */ + kex->kex[KEX_C25519_SHA256] = kex_gen_server; + kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; +@@ -1885,8 +1920,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, + u_char *p; + int r; + +- if (!options.gss_authentication) +- fatal_f("GSSAPI authentication not enabled"); ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); + + if ((r = sshbuf_get_string(m, &p, &len)) != 0) + fatal_fr(r, "parse"); +@@ -1918,8 +1953,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh + OM_uint32 flags = 0; /* GSI needs this */ + int r; + +- if (!options.gss_authentication) +- fatal_f("GSSAPI authentication not enabled"); ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); + + if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0) + fatal_fr(r, "ssh_gssapi_get_buffer_desc"); +@@ -1939,6 +1974,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); ++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN, 1); + } + return (0); + } +@@ -1950,8 +1986,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, + OM_uint32 ret; + int r; + +- if (!options.gss_authentication) +- fatal_f("GSSAPI authentication not enabled"); ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); + + if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 || + (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0) +@@ -1977,13 +2013,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, + int + mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) + { +- int r, authenticated; ++ int r, authenticated, kex; + const char *displayname; + +- if (!options.gss_authentication) +- fatal_f("GSSAPI authentication not enabled"); ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); + +- authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user); ++ if ((r = sshbuf_get_u32(m, &kex)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ authenticated = authctxt->valid && ++ ssh_gssapi_userok(authctxt->user, authctxt->pw, kex); + + sshbuf_reset(m); + if ((r = sshbuf_put_u32(m, authenticated)) != 0) +@@ -1992,7 +2032,11 @@ mm_answer_gss_userok(struct ssh *ssh, in + debug3_f("sending result %d", authenticated); + mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m); + +- auth_method = "gssapi-with-mic"; ++ if (kex) { ++ auth_method = "gssapi-keyex"; ++ } else { ++ auth_method = "gssapi-with-mic"; ++ } + + if ((displayname = ssh_gssapi_displayname()) != NULL) + auth2_record_info(authctxt, "%s", displayname); +@@ -2000,5 +2044,84 @@ mm_answer_gss_userok(struct ssh *ssh, in + /* Monitor loop will terminate if authenticated */ + return (authenticated); + } ++ ++int ++mm_answer_gss_sign(struct ssh *ssh, int socket, struct sshbuf *m) ++{ ++ gss_buffer_desc data; ++ gss_buffer_desc hash = GSS_C_EMPTY_BUFFER; ++ OM_uint32 major, minor; ++ size_t len; ++ u_char *p = NULL; ++ int r; ++ ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); ++ ++ if ((r = sshbuf_get_string(m, &p, &len)) != 0) ++ fatal_fr(r, "buffer error"); ++ data.value = p; ++ data.length = len; ++ /* Lengths of SHA-1, SHA-256 and SHA-512 hashes that are used */ ++ if (data.length != 20 && data.length != 32 && data.length != 64) ++ fatal_f("data length incorrect: %d", (int) data.length); ++ ++ /* Save the session ID on the first time around */ ++ if (session_id2_len == 0) { ++ session_id2_len = data.length; ++ session_id2 = xmalloc(session_id2_len); ++ memcpy(session_id2, data.value, session_id2_len); ++ } ++ major = ssh_gssapi_sign(gsscontext, &data, &hash); ++ ++ free(data.value); ++ ++ sshbuf_reset(m); ++ ++ if ((r = sshbuf_put_u32(m, major)) != 0 || ++ (r = sshbuf_put_string(m, hash.value, hash.length)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ mm_request_send(socket, MONITOR_ANS_GSSSIGN, m); ++ ++ gss_release_buffer(&minor, &hash); ++ ++ /* Turn on getpwnam permissions */ ++ monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1); ++ ++ /* And credential updating, for when rekeying */ ++ monitor_permit(mon_dispatch, MONITOR_REQ_GSSUPCREDS, 1); ++ ++ return (0); ++} ++ ++int ++mm_answer_gss_updatecreds(struct ssh *ssh, int socket, struct sshbuf *m) { ++ ssh_gssapi_ccache store; ++ int r, ok; ++ ++ if (!options.gss_authentication && !options.gss_keyex) ++ fatal_f("GSSAPI not enabled"); ++ ++ if ((r = sshbuf_get_string(m, (u_char **)&store.filename, NULL)) != 0 || ++ (r = sshbuf_get_string(m, (u_char **)&store.envvar, NULL)) != 0 || ++ (r = sshbuf_get_string(m, (u_char **)&store.envval, NULL)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ ok = ssh_gssapi_update_creds(&store); ++ ++ free(store.filename); ++ free(store.envvar); ++ free(store.envval); ++ ++ sshbuf_reset(m); ++ if ((r = sshbuf_put_u32(m, ok)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ mm_request_send(socket, MONITOR_ANS_GSSUPCREDS, m); ++ ++ return(0); ++} ++ + #endif /* GSSAPI */ + +diff --color -ruNp a/monitor.h b/monitor.h +--- a/monitor.h 2024-09-16 11:45:56.861133305 +0200 ++++ b/monitor.h 2024-09-16 11:46:34.696939926 +0200 +@@ -67,6 +67,8 @@ enum monitor_reqtype { + MONITOR_REQ_PAM_FREE_CTX = 110, MONITOR_ANS_PAM_FREE_CTX = 111, + MONITOR_REQ_AUDIT_EVENT = 112, MONITOR_REQ_AUDIT_COMMAND = 113, + ++ MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151, ++ MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153, + }; + + struct ssh; +diff --color -ruNp a/monitor_wrap.c b/monitor_wrap.c +--- a/monitor_wrap.c 2024-09-16 11:45:56.862133326 +0200 ++++ b/monitor_wrap.c 2024-09-16 11:46:34.697939947 +0200 +@@ -1075,13 +1075,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss + } + + int +-mm_ssh_gssapi_userok(char *user) ++mm_ssh_gssapi_userok(char *user, struct passwd *pw, int kex) + { + struct sshbuf *m; + int r, authenticated = 0; + + if ((m = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); ++ if ((r = sshbuf_put_u32(m, kex)) != 0) ++ fatal_fr(r, "buffer error"); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, m); + mm_request_receive_expect(pmonitor->m_recvfd, +@@ -1094,6 +1096,59 @@ mm_ssh_gssapi_userok(char *user) + debug3_f("user %sauthenticated", authenticated ? "" : "not "); + return (authenticated); + } ++ ++OM_uint32 ++mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash) ++{ ++ struct sshbuf *m; ++ OM_uint32 major; ++ int r; ++ ++ if ((m = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new failed"); ++ if ((r = sshbuf_put_string(m, data->value, data->length)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSIGN, m); ++ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSIGN, m); ++ ++ if ((r = sshbuf_get_u32(m, &major)) != 0 || ++ (r = ssh_gssapi_get_buffer_desc(m, hash)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ sshbuf_free(m); ++ ++ return (major); ++} ++ ++int ++mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *store) ++{ ++ struct sshbuf *m; ++ int r, ok; ++ ++ if ((m = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new failed"); ++ ++ if ((r = sshbuf_put_cstring(m, ++ store->filename ? store->filename : "")) != 0 || ++ (r = sshbuf_put_cstring(m, ++ store->envvar ? store->envvar : "")) != 0 || ++ (r = sshbuf_put_cstring(m, ++ store->envval ? store->envval : "")) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUPCREDS, m); ++ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUPCREDS, m); ++ ++ if ((r = sshbuf_get_u32(m, &ok)) != 0) ++ fatal_fr(r, "buffer error"); ++ ++ sshbuf_free(m); ++ ++ return (ok); ++} ++ + #endif /* GSSAPI */ + + /* +diff --color -ruNp a/monitor_wrap.h b/monitor_wrap.h +--- a/monitor_wrap.h 2024-09-16 11:45:56.862133326 +0200 ++++ b/monitor_wrap.h 2024-09-16 11:46:34.697939947 +0200 +@@ -67,8 +67,10 @@ void mm_decode_activate_server_options(s + OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID); + OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *, + gss_buffer_desc *, gss_buffer_desc *, OM_uint32 *); +-int mm_ssh_gssapi_userok(char *user); ++int mm_ssh_gssapi_userok(char *user, struct passwd *, int kex); + OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); ++OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); ++int mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *); + #endif + + #ifdef USE_PAM +diff --color -ruNp a/readconf.c b/readconf.c +--- a/readconf.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/readconf.c 2024-09-16 11:46:34.699939990 +0200 +@@ -70,6 +70,7 @@ + #include "uidswap.h" + #include "myproposal.h" + #include "digest.h" ++#include "ssh-gss.h" + + /* Format of the configuration file: + +@@ -164,6 +165,8 @@ typedef enum { + oClearAllForwardings, oNoHostAuthenticationForLocalhost, + oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, + oAddressFamily, oGssAuthentication, oGssDelegateCreds, ++ oGssTrustDns, oGssKeyEx, oGssClientIdentity, oGssRenewalRekey, ++ oGssServerIdentity, oGssKexAlgorithms, + oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, + oSendEnv, oSetEnv, oControlPath, oControlMaster, oControlPersist, + oHashKnownHosts, +@@ -210,10 +213,22 @@ static struct { + /* Sometimes-unsupported options */ + #if defined(GSSAPI) + { "gssapiauthentication", oGssAuthentication }, ++ { "gssapikeyexchange", oGssKeyEx }, + { "gssapidelegatecredentials", oGssDelegateCreds }, ++ { "gssapitrustdns", oGssTrustDns }, ++ { "gssapiclientidentity", oGssClientIdentity }, ++ { "gssapiserveridentity", oGssServerIdentity }, ++ { "gssapirenewalforcesrekey", oGssRenewalRekey }, ++ { "gssapikexalgorithms", oGssKexAlgorithms }, + # else + { "gssapiauthentication", oUnsupported }, ++ { "gssapikeyexchange", oUnsupported }, + { "gssapidelegatecredentials", oUnsupported }, ++ { "gssapitrustdns", oUnsupported }, ++ { "gssapiclientidentity", oUnsupported }, ++ { "gssapiserveridentity", oUnsupported }, ++ { "gssapirenewalforcesrekey", oUnsupported }, ++ { "gssapikexalgorithms", oUnsupported }, + #endif + #ifdef ENABLE_PKCS11 + { "pkcs11provider", oPKCS11Provider }, +@@ -1227,10 +1242,42 @@ parse_time: + intptr = &options->gss_authentication; + goto parse_flag; + ++ case oGssKeyEx: ++ intptr = &options->gss_keyex; ++ goto parse_flag; ++ + case oGssDelegateCreds: + intptr = &options->gss_deleg_creds; + goto parse_flag; + ++ case oGssTrustDns: ++ intptr = &options->gss_trust_dns; ++ goto parse_flag; ++ ++ case oGssClientIdentity: ++ charptr = &options->gss_client_identity; ++ goto parse_string; ++ ++ case oGssServerIdentity: ++ charptr = &options->gss_server_identity; ++ goto parse_string; ++ ++ case oGssRenewalRekey: ++ intptr = &options->gss_renewal_rekey; ++ goto parse_flag; ++ ++ case oGssKexAlgorithms: ++ arg = argv_next(&ac, &av); ++ if (!arg || *arg == '\0') ++ fatal("%.200s line %d: Missing argument.", ++ filename, linenum); ++ if (!kex_gss_names_valid(arg)) ++ fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.", ++ filename, linenum, arg ? arg : ""); ++ if (*activep && options->gss_kex_algorithms == NULL) ++ options->gss_kex_algorithms = xstrdup(arg); ++ break; ++ + case oBatchMode: + intptr = &options->batch_mode; + goto parse_flag; +@@ -2542,7 +2589,13 @@ initialize_options(Options * options) + options->fwd_opts.streamlocal_bind_unlink = -1; + options->pubkey_authentication = -1; + options->gss_authentication = -1; ++ options->gss_keyex = -1; + options->gss_deleg_creds = -1; ++ options->gss_trust_dns = -1; ++ options->gss_renewal_rekey = -1; ++ options->gss_client_identity = NULL; ++ options->gss_server_identity = NULL; ++ options->gss_kex_algorithms = NULL; + options->password_authentication = -1; + options->kbd_interactive_authentication = -1; + options->kbd_interactive_devices = NULL; +@@ -2705,8 +2758,18 @@ fill_default_options(Options * options) + options->pubkey_authentication = SSH_PUBKEY_AUTH_ALL; + if (options->gss_authentication == -1) + options->gss_authentication = 0; ++ if (options->gss_keyex == -1) ++ options->gss_keyex = 0; + if (options->gss_deleg_creds == -1) + options->gss_deleg_creds = 0; ++ if (options->gss_trust_dns == -1) ++ options->gss_trust_dns = 0; ++ if (options->gss_renewal_rekey == -1) ++ options->gss_renewal_rekey = 0; ++#ifdef GSSAPI ++ if (options->gss_kex_algorithms == NULL) ++ options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX); ++#endif + if (options->password_authentication == -1) + options->password_authentication = 1; + if (options->kbd_interactive_authentication == -1) +@@ -3533,7 +3596,14 @@ dump_client_config(Options *o, const cha + dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports); + #ifdef GSSAPI + dump_cfg_fmtint(oGssAuthentication, o->gss_authentication); ++ dump_cfg_fmtint(oGssKeyEx, o->gss_keyex); + dump_cfg_fmtint(oGssDelegateCreds, o->gss_deleg_creds); ++ dump_cfg_fmtint(oGssTrustDns, o->gss_trust_dns); ++ dump_cfg_fmtint(oGssRenewalRekey, o->gss_renewal_rekey); ++ dump_cfg_string(oGssClientIdentity, o->gss_client_identity); ++ dump_cfg_string(oGssServerIdentity, o->gss_server_identity); ++ dump_cfg_string(oGssKexAlgorithms, o->gss_kex_algorithms ? ++ o->gss_kex_algorithms : GSS_KEX_DEFAULT_KEX); + #endif /* GSSAPI */ + dump_cfg_fmtint(oHashKnownHosts, o->hash_known_hosts); + dump_cfg_fmtint(oHostbasedAuthentication, o->hostbased_authentication); +diff --color -ruNp a/readconf.h b/readconf.h +--- a/readconf.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/readconf.h 2024-09-16 11:46:34.699939990 +0200 +@@ -40,7 +40,13 @@ typedef struct { + int pubkey_authentication; /* Try ssh2 pubkey authentication. */ + int hostbased_authentication; /* ssh2's rhosts_rsa */ + int gss_authentication; /* Try GSS authentication */ ++ int gss_keyex; /* Try GSS key exchange */ + int gss_deleg_creds; /* Delegate GSS credentials */ ++ int gss_trust_dns; /* Trust DNS for GSS canonicalization */ ++ int gss_renewal_rekey; /* Credential renewal forces rekey */ ++ char *gss_client_identity; /* Principal to initiate GSSAPI with */ ++ char *gss_server_identity; /* GSSAPI target principal */ ++ char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */ + int password_authentication; /* Try password + * authentication. */ + int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ +diff --color -ruNp a/servconf.c b/servconf.c +--- a/servconf.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/servconf.c 2024-09-16 11:46:34.700940011 +0200 +@@ -68,6 +68,7 @@ + #include "auth.h" + #include "myproposal.h" + #include "digest.h" ++#include "ssh-gss.h" + + #if !defined(SSHD_PAM_SERVICE) + # define SSHD_PAM_SERVICE "sshd" +@@ -137,8 +138,11 @@ initialize_server_options(ServerOptions + options->kerberos_ticket_cleanup = -1; + options->kerberos_get_afs_token = -1; + options->gss_authentication=-1; ++ options->gss_keyex = -1; + options->gss_cleanup_creds = -1; + options->gss_strict_acceptor = -1; ++ options->gss_store_rekey = -1; ++ options->gss_kex_algorithms = NULL; + options->password_authentication = -1; + options->kbd_interactive_authentication = -1; + options->permit_empty_passwd = -1; +@@ -376,10 +380,18 @@ fill_default_server_options(ServerOption + options->kerberos_get_afs_token = 0; + if (options->gss_authentication == -1) + options->gss_authentication = 0; ++ if (options->gss_keyex == -1) ++ options->gss_keyex = 0; + if (options->gss_cleanup_creds == -1) + options->gss_cleanup_creds = 1; + if (options->gss_strict_acceptor == -1) + options->gss_strict_acceptor = 1; ++ if (options->gss_store_rekey == -1) ++ options->gss_store_rekey = 0; ++#ifdef GSSAPI ++ if (options->gss_kex_algorithms == NULL) ++ options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX); ++#endif + if (options->password_authentication == -1) + options->password_authentication = 1; + if (options->kbd_interactive_authentication == -1) +@@ -558,6 +570,7 @@ typedef enum { + sPerSourcePenalties, sPerSourcePenaltyExemptList, + sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, + sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, ++ sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey, + sAcceptEnv, sSetEnv, sPermitTunnel, + sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, + sUsePrivilegeSeparation, sAllowAgentForwarding, +@@ -643,12 +656,22 @@ static struct { + #ifdef GSSAPI + { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL }, + { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL }, ++ { "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL }, + { "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL }, ++ { "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL }, ++ { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL }, ++ { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL }, + #else + { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, + { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, ++ { "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL }, + { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL }, ++ { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL }, ++ { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL }, ++ { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL }, + #endif ++ { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL }, ++ { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL }, + { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL }, + { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, + { "challengeresponseauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, /* alias */ +@@ -1585,6 +1608,10 @@ process_server_config_line_depth(ServerO + intptr = &options->gss_authentication; + goto parse_flag; + ++ case sGssKeyEx: ++ intptr = &options->gss_keyex; ++ goto parse_flag; ++ + case sGssCleanupCreds: + intptr = &options->gss_cleanup_creds; + goto parse_flag; +@@ -1593,6 +1620,22 @@ process_server_config_line_depth(ServerO + intptr = &options->gss_strict_acceptor; + goto parse_flag; + ++ case sGssStoreRekey: ++ intptr = &options->gss_store_rekey; ++ goto parse_flag; ++ ++ case sGssKexAlgorithms: ++ arg = argv_next(&ac, &av); ++ if (!arg || *arg == '\0') ++ fatal("%.200s line %d: Missing argument.", ++ filename, linenum); ++ if (!kex_gss_names_valid(arg)) ++ fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.", ++ filename, linenum, arg ? arg : ""); ++ if (*activep && options->gss_kex_algorithms == NULL) ++ options->gss_kex_algorithms = xstrdup(arg); ++ break; ++ + case sPasswordAuthentication: + intptr = &options->password_authentication; + goto parse_flag; +@@ -3178,6 +3221,10 @@ dump_config(ServerOptions *o) + #ifdef GSSAPI + dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); + dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); ++ dump_cfg_fmtint(sGssKeyEx, o->gss_keyex); ++ dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor); ++ dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey); ++ dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms); + #endif + dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); + dump_cfg_fmtint(sKbdInteractiveAuthentication, +diff --color -ruNp a/servconf.h b/servconf.h +--- a/servconf.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/servconf.h 2024-09-16 11:46:34.700940011 +0200 +@@ -149,8 +149,11 @@ typedef struct { + int kerberos_get_afs_token; /* If true, try to get AFS token if + * authenticated with Kerberos. */ + int gss_authentication; /* If true, permit GSSAPI authentication */ ++ int gss_keyex; /* If true, permit GSSAPI key exchange */ + int gss_cleanup_creds; /* If true, destroy cred cache on logout */ + int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */ ++ int gss_store_rekey; ++ char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */ + int password_authentication; /* If true, permit password + * authentication. */ + int kbd_interactive_authentication; /* If true, permit */ +diff --color -ruNp a/session.c b/session.c +--- a/session.c 2024-09-16 11:45:56.866133411 +0200 ++++ b/session.c 2024-09-16 11:46:34.701940032 +0200 +@@ -2674,13 +2674,19 @@ do_cleanup(struct ssh *ssh, Authctxt *au + + #ifdef KRB5 + if (options.kerberos_ticket_cleanup && +- authctxt->krb5_ctx) ++ authctxt->krb5_ctx) { ++ temporarily_use_uid(authctxt->pw); + krb5_cleanup_proc(authctxt); ++ restore_uid(); ++ } + #endif + + #ifdef GSSAPI +- if (options.gss_cleanup_creds) ++ if (options.gss_cleanup_creds) { ++ temporarily_use_uid(authctxt->pw); + ssh_gssapi_cleanup_creds(); ++ restore_uid(); ++ } + #endif + + /* remove agent socket */ +diff --color -ruNp a/ssh.1 b/ssh.1 +--- a/ssh.1 2024-09-16 11:45:56.875133603 +0200 ++++ b/ssh.1 2024-09-16 11:46:34.701940032 +0200 +@@ -536,7 +536,13 @@ For full details of the options listed b + .It GatewayPorts + .It GlobalKnownHostsFile + .It GSSAPIAuthentication ++.It GSSAPIKeyExchange ++.It GSSAPIClientIdentity + .It GSSAPIDelegateCredentials ++.It GSSAPIKexAlgorithms ++.It GSSAPIRenewalForcesRekey ++.It GSSAPIServerIdentity ++.It GSSAPITrustDns + .It HashKnownHosts + .It Host + .It HostbasedAcceptedAlgorithms +@@ -624,6 +630,8 @@ flag), + (supported message integrity codes), + .Ar kex + (key exchange algorithms), ++.Ar kex-gss ++(GSSAPI key exchange algorithms), + .Ar key + (key types), + .Ar key-ca-sign +diff --color -ruNp a/ssh.c b/ssh.c +--- a/ssh.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/ssh.c 2024-09-16 11:46:34.702940054 +0200 +@@ -827,6 +827,8 @@ main(int ac, char **av) + else if (strcmp(optarg, "kex") == 0 || + strcasecmp(optarg, "KexAlgorithms") == 0) + cp = kex_alg_list('\n'); ++ else if (strcmp(optarg, "kex-gss") == 0) ++ cp = kex_gss_alg_list('\n'); + else if (strcmp(optarg, "key") == 0) + cp = sshkey_alg_list(0, 0, 0, '\n'); + else if (strcmp(optarg, "key-cert") == 0) +@@ -857,8 +859,8 @@ main(int ac, char **av) + } else if (strcmp(optarg, "help") == 0) { + cp = xstrdup( + "cipher\ncipher-auth\ncompression\nkex\n" +- "key\nkey-cert\nkey-plain\nkey-sig\nmac\n" +- "protocol-version\nsig"); ++ "kex-gss\nkey\nkey-cert\nkey-plain\n" ++ "key-sig\nmac\nprotocol-version\nsig"); + } + if (cp == NULL) + fatal("Unsupported query \"%s\"", optarg); +diff --color -ruNp a/ssh_config b/ssh_config +--- a/ssh_config 2024-09-16 11:45:56.884133795 +0200 ++++ b/ssh_config 2024-09-16 11:46:34.702940054 +0200 +@@ -24,6 +24,8 @@ + # HostbasedAuthentication no + # GSSAPIAuthentication no + # GSSAPIDelegateCredentials no ++# GSSAPIKeyExchange no ++# GSSAPITrustDNS no + # BatchMode no + # CheckHostIP no + # AddressFamily any +diff --color -ruNp a/ssh_config.5 b/ssh_config.5 +--- a/ssh_config.5 2024-07-01 06:36:28.000000000 +0200 ++++ b/ssh_config.5 2024-09-16 11:46:34.703940075 +0200 +@@ -938,10 +938,68 @@ The default is + Specifies whether user authentication based on GSSAPI is allowed. + The default is + .Cm no . ++.It Cm GSSAPIClientIdentity ++If set, specifies the GSSAPI client identity that ssh should use when ++connecting to the server. The default is unset, which means that the default ++identity will be used. + .It Cm GSSAPIDelegateCredentials + Forward (delegate) credentials to the server. + The default is + .Cm no . ++.It Cm GSSAPIKeyExchange ++Specifies whether key exchange based on GSSAPI may be used. When using ++GSSAPI key exchange the server need not have a host key. ++The default is ++.Dq no . ++.It Cm GSSAPIRenewalForcesRekey ++If set to ++.Dq yes ++then renewal of the client's GSSAPI credentials will force the rekeying of the ++ssh connection. With a compatible server, this will delegate the renewed ++credentials to a session on the server. ++.Pp ++Checks are made to ensure that credentials are only propagated when the new ++credentials match the old ones on the originating client and where the ++receiving server still has the old set in its cache. ++.Pp ++The default is ++.Dq no . ++.Pp ++For this to work ++.Cm GSSAPIKeyExchange ++needs to be enabled in the server and also used by the client. ++.It Cm GSSAPIServerIdentity ++If set, specifies the GSSAPI server identity that ssh should expect when ++connecting to the server. The default is unset, which means that the ++expected GSSAPI server identity will be determined from the target ++hostname. ++.It Cm GSSAPITrustDns ++Set to ++.Dq yes ++to indicate that the DNS is trusted to securely canonicalize ++the name of the host being connected to. If ++.Dq no , ++the hostname entered on the ++command line will be passed untouched to the GSSAPI library. ++The default is ++.Dq no . ++.It Cm GSSAPIKexAlgorithms ++The list of key exchange algorithms that are offered for GSSAPI ++key exchange. Possible values are ++.Bd -literal -offset 3n ++gss-gex-sha1-, ++gss-group1-sha1-, ++gss-group14-sha1-, ++gss-group14-sha256-, ++gss-group16-sha512-, ++gss-nistp256-sha256-, ++gss-curve25519-sha256- ++.Ed ++.Pp ++The default is ++.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-, ++gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1- . ++This option only applies to connections using GSSAPI. + .It Cm HashKnownHosts + Indicates that + .Xr ssh 1 +diff --color -ruNp a/sshconnect2.c b/sshconnect2.c +--- a/sshconnect2.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/sshconnect2.c 2024-09-16 11:46:34.703940075 +0200 +@@ -222,6 +222,11 @@ ssh_kex2(struct ssh *ssh, char *host, st + char *all_key, *hkalgs = NULL; + int r, use_known_hosts_order = 0; + ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ char *orig = NULL, *gss = NULL; ++ char *gss_host = NULL; ++#endif ++ + xxx_host = host; + xxx_hostaddr = hostaddr; + xxx_conn_info = cinfo; +@@ -255,6 +260,42 @@ ssh_kex2(struct ssh *ssh, char *host, st + compression_alg_list(options.compression), + hkalgs ? hkalgs : options.hostkeyalgorithms); + ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ if (options.gss_keyex) { ++ /* Add the GSSAPI mechanisms currently supported on this ++ * client to the key exchange algorithm proposal */ ++ orig = myproposal[PROPOSAL_KEX_ALGS]; ++ ++ if (options.gss_server_identity) { ++ gss_host = xstrdup(options.gss_server_identity); ++ } else if (options.gss_trust_dns) { ++ gss_host = remote_hostname(ssh); ++ /* Fall back to specified host if we are using proxy command ++ * and can not use DNS on that socket */ ++ if (strcmp(gss_host, "UNKNOWN") == 0) { ++ free(gss_host); ++ gss_host = xstrdup(host); ++ } ++ } else { ++ gss_host = xstrdup(host); ++ } ++ ++ gss = ssh_gssapi_client_mechanisms(gss_host, ++ options.gss_client_identity, options.gss_kex_algorithms); ++ if (gss) { ++ debug("Offering GSSAPI proposal: %s", gss); ++ xasprintf(&myproposal[PROPOSAL_KEX_ALGS], ++ "%s,%s", gss, orig); ++ ++ /* If we've got GSSAPI algorithms, then we also support the ++ * 'null' hostkey, as a last resort */ ++ orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]; ++ xasprintf(&myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS], ++ "%s,null", orig); ++ } ++ } ++#endif ++ + free(hkalgs); + + /* start key exchange */ +@@ -271,15 +312,45 @@ ssh_kex2(struct ssh *ssh, char *host, st + # ifdef OPENSSL_HAS_ECC + ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client; + # endif +-#endif ++# ifdef GSSAPI ++ if (options.gss_keyex) { ++ ssh->kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client; ++ ssh->kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_client; ++ ssh->kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_client; ++ ssh->kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_client; ++ ssh->kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_client; ++ ssh->kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_client; ++ ssh->kex->kex[KEX_GSS_C25519_SHA256] = kexgss_client; ++ } ++# endif ++#endif /* WITH_OPENSSL */ + ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; + ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client; + ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client; + ssh->kex->verify_host_key=&verify_host_key_callback; + ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ if (options.gss_keyex) { ++ ssh->kex->gss_deleg_creds = options.gss_deleg_creds; ++ ssh->kex->gss_trust_dns = options.gss_trust_dns; ++ ssh->kex->gss_client = options.gss_client_identity; ++ ssh->kex->gss_host = gss_host; ++ } ++#endif ++ + ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &ssh->kex->done); + kex_proposal_free_entries(myproposal); + ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ /* repair myproposal after it was crumpled by the */ ++ /* ext-info removal above */ ++ if (gss) { ++ orig = myproposal[PROPOSAL_KEX_ALGS]; ++ xasprintf(&myproposal[PROPOSAL_KEX_ALGS], ++ "%s,%s", gss, orig); ++ free(gss); ++ } ++#endif + #ifdef DEBUG_KEXDH + /* send 1st encrypted/maced/compressed message */ + if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 || +@@ -368,6 +439,7 @@ static int input_gssapi_response(int typ + static int input_gssapi_token(int type, u_int32_t, struct ssh *); + static int input_gssapi_error(int, u_int32_t, struct ssh *); + static int input_gssapi_errtok(int, u_int32_t, struct ssh *); ++static int userauth_gsskeyex(struct ssh *); + #endif + + void userauth(struct ssh *, char *); +@@ -384,6 +456,11 @@ static char *authmethods_get(void); + + Authmethod authmethods[] = { + #ifdef GSSAPI ++ {"gssapi-keyex", ++ userauth_gsskeyex, ++ NULL, ++ &options.gss_keyex, ++ NULL}, + {"gssapi-with-mic", + userauth_gssapi, + userauth_gssapi_cleanup, +@@ -755,12 +832,32 @@ userauth_gssapi(struct ssh *ssh) + OM_uint32 min; + int r, ok = 0; + gss_OID mech = NULL; ++ char *gss_host = NULL; ++ ++ if (options.gss_server_identity) { ++ gss_host = xstrdup(options.gss_server_identity); ++ } else if (options.gss_trust_dns) { ++ gss_host = remote_hostname(ssh); ++ /* Fall back to specified host if we are using proxy command ++ * and can not use DNS on that socket */ ++ if (strcmp(gss_host, "UNKNOWN") == 0) { ++ free(gss_host); ++ gss_host = xstrdup(authctxt->host); ++ } ++ } else { ++ gss_host = xstrdup(authctxt->host); ++ } + + /* Try one GSSAPI method at a time, rather than sending them all at + * once. */ + + if (authctxt->gss_supported_mechs == NULL) +- gss_indicate_mechs(&min, &authctxt->gss_supported_mechs); ++ if (GSS_ERROR(gss_indicate_mechs(&min, ++ &authctxt->gss_supported_mechs))) { ++ authctxt->gss_supported_mechs = NULL; ++ free(gss_host); ++ return 0; ++ } + + /* Check to see whether the mechanism is usable before we offer it */ + while (authctxt->mech_tried < authctxt->gss_supported_mechs->count && +@@ -769,13 +866,15 @@ userauth_gssapi(struct ssh *ssh) + elements[authctxt->mech_tried]; + /* My DER encoding requires length<128 */ + if (mech->length < 128 && ssh_gssapi_check_mechanism(&gssctxt, +- mech, authctxt->host)) { ++ mech, gss_host, options.gss_client_identity)) { + ok = 1; /* Mechanism works */ + } else { + authctxt->mech_tried++; + } + } + ++ free(gss_host); ++ + if (!ok || mech == NULL) + return 0; + +@@ -1009,6 +1108,55 @@ input_gssapi_error(int type, u_int32_t p + free(lang); + return r; + } ++ ++int ++userauth_gsskeyex(struct ssh *ssh) ++{ ++ struct sshbuf *b = NULL; ++ Authctxt *authctxt = ssh->authctxt; ++ gss_buffer_desc gssbuf; ++ gss_buffer_desc mic = GSS_C_EMPTY_BUFFER; ++ OM_uint32 ms; ++ int r; ++ ++ static int attempt = 0; ++ if (attempt++ >= 1) ++ return (0); ++ ++ if (gss_kex_context == NULL) { ++ debug("No valid Key exchange context"); ++ return (0); ++ } ++ ++ if ((b = sshbuf_new()) == NULL) ++ fatal_f("sshbuf_new failed"); ++ ++ ssh_gssapi_buildmic(b, authctxt->server_user, authctxt->service, ++ "gssapi-keyex", ssh->kex->session_id); ++ ++ if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL) ++ fatal_f("sshbuf_mutable_ptr failed"); ++ gssbuf.length = sshbuf_len(b); ++ ++ if (GSS_ERROR(ssh_gssapi_sign(gss_kex_context, &gssbuf, &mic))) { ++ sshbuf_free(b); ++ return (0); ++ } ++ ++ if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_REQUEST)) != 0 || ++ (r = sshpkt_put_cstring(ssh, authctxt->server_user)) != 0 || ++ (r = sshpkt_put_cstring(ssh, authctxt->service)) != 0 || ++ (r = sshpkt_put_cstring(ssh, authctxt->method->name)) != 0 || ++ (r = sshpkt_put_string(ssh, mic.value, mic.length)) != 0 || ++ (r = sshpkt_send(ssh)) != 0) ++ fatal_fr(r, "parsing"); ++ ++ sshbuf_free(b); ++ gss_release_buffer(&ms, &mic); ++ ++ return (1); ++} ++ + #endif /* GSSAPI */ + + static int +diff --color -ruNp a/sshd.c b/sshd.c +--- a/sshd.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/sshd.c 2024-09-16 11:46:34.704940096 +0200 +@@ -1551,7 +1551,8 @@ main(int ac, char **av) + free(fp); + } + accumulate_host_timing_secret(cfg, NULL); +- if (!sensitive_data.have_ssh2_key) { ++ /* The GSSAPI key exchange can run without a host key */ ++ if (!sensitive_data.have_ssh2_key && !options.gss_keyex) { + logit("sshd: no hostkeys available -- exiting."); + exit(1); + } +diff --color -ruNp a/sshd_config b/sshd_config +--- a/sshd_config 2024-09-16 11:45:56.888133880 +0200 ++++ b/sshd_config 2024-09-16 11:46:34.704940096 +0200 +@@ -77,6 +77,8 @@ AuthorizedKeysFile .ssh/authorized_keys + # GSSAPI options + #GSSAPIAuthentication no + #GSSAPICleanupCredentials yes ++#GSSAPIStrictAcceptorCheck yes ++#GSSAPIKeyExchange no + + # Set this to 'yes' to enable PAM authentication, account processing, + # and session processing. If this is enabled, PAM authentication will +diff --color -ruNp a/sshd_config.5 b/sshd_config.5 +--- a/sshd_config.5 2024-09-16 11:45:56.885133816 +0200 ++++ b/sshd_config.5 2024-09-16 11:46:34.704940096 +0200 +@@ -739,6 +739,11 @@ Specifies whether to automatically destr + on logout. + The default is + .Cm yes . ++.It Cm GSSAPIKeyExchange ++Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange ++doesn't rely on ssh keys to verify host identity. ++The default is ++.Cm no . + .It Cm GSSAPIStrictAcceptorCheck + Determines whether to be strict about the identity of the GSSAPI acceptor + a client authenticates against. +@@ -753,6 +758,32 @@ machine's default store. + This facility is provided to assist with operation on multi homed machines. + The default is + .Cm yes . ++.It Cm GSSAPIStoreCredentialsOnRekey ++Controls whether the user's GSSAPI credentials should be updated following a ++successful connection rekeying. This option can be used to accepted renewed ++or updated credentials from a compatible client. The default is ++.Dq no . ++.Pp ++For this to work ++.Cm GSSAPIKeyExchange ++needs to be enabled in the server and also used by the client. ++.It Cm GSSAPIKexAlgorithms ++The list of key exchange algorithms that are accepted by GSSAPI ++key exchange. Possible values are ++.Bd -literal -offset 3n ++gss-gex-sha1-, ++gss-group1-sha1-, ++gss-group14-sha1-, ++gss-group14-sha256-, ++gss-group16-sha512-, ++gss-nistp256-sha256-, ++gss-curve25519-sha256- ++.Ed ++.Pp ++The default is ++.Dq gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-, ++gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1- . ++This option only applies to connections using GSSAPI. + .It Cm HostbasedAcceptedAlgorithms + Specifies the signature algorithms that will be accepted for hostbased + authentication as a list of comma-separated patterns. +diff --color -ruNp a/sshd-session.c b/sshd-session.c +--- a/sshd-session.c 2024-09-16 11:45:56.888133880 +0200 ++++ b/sshd-session.c 2024-09-16 11:46:34.705940118 +0200 +@@ -660,8 +660,8 @@ notify_hostkeys(struct ssh *ssh) + } + debug3_f("sent %u hostkeys", nkeys); + if (nkeys == 0) +- fatal_f("no hostkeys"); +- if ((r = sshpkt_send(ssh)) != 0) ++ debug3_f("no hostkeys"); ++ else if ((r = sshpkt_send(ssh)) != 0) + sshpkt_fatal(ssh, r, "%s: send", __func__); + sshbuf_free(buf); + } +@@ -1180,8 +1180,9 @@ main(int ac, char **av) + break; + } + } +- if (!have_key) +- fatal("internal error: monitor received no hostkeys"); ++ /* The GSSAPI key exchange can run without a host key */ ++ if (!have_key && !options.gss_keyex) ++ fatal("internal error: monitor received no hostkeys and GSS KEX is not configured"); + + /* Ensure that umask disallows at least group and world write */ + new_umask = umask(0077) | 0022; +@@ -1462,6 +1463,48 @@ do_ssh2_kex(struct ssh *ssh) + + free(hkalgs); + ++#if defined(GSSAPI) && defined(WITH_OPENSSL) ++ { ++ char *orig; ++ char *gss = NULL; ++ char *newstr = NULL; ++ orig = myproposal[PROPOSAL_KEX_ALGS]; ++ ++ /* ++ * If we don't have a host key, then there's no point advertising ++ * the other key exchange algorithms ++ */ ++ ++ if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]) == 0) ++ orig = NULL; ++ ++ if (options.gss_keyex) ++ gss = ssh_gssapi_server_mechanisms(); ++ else ++ gss = NULL; ++ ++ if (gss && orig) ++ xasprintf(&newstr, "%s,%s", gss, orig); ++ else if (gss) ++ xasprintf(&newstr, "%s,%s", gss, "kex-strict-s-v00@openssh.com"); ++ else if (orig) ++ newstr = orig; ++ ++ /* ++ * If we've got GSSAPI mechanisms, then we've got the 'null' host ++ * key alg, but we can't tell people about it unless its the only ++ * host key algorithm we support ++ */ ++ if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0) ++ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = xstrdup("null"); ++ ++ if (newstr) ++ myproposal[PROPOSAL_KEX_ALGS] = newstr; ++ else ++ fatal("No supported key exchange algorithms"); ++ } ++#endif ++ + /* start key exchange */ + if ((r = kex_setup(ssh, myproposal)) != 0) + fatal_r(r, "kex_setup"); +@@ -1479,7 +1522,18 @@ do_ssh2_kex(struct ssh *ssh) + #ifdef OPENSSL_HAS_ECC + kex->kex[KEX_ECDH_SHA2] = kex_gen_server; + #endif +-#endif ++# ifdef GSSAPI ++ if (options.gss_keyex) { ++ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; ++ kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; ++ kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server; ++ kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server; ++ kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server; ++ kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server; ++ kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server; ++ } ++# endif ++#endif /* WITH_OPENSSL */ + kex->kex[KEX_C25519_SHA256] = kex_gen_server; + kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; + kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server; +diff --color -ruNp a/ssh-gss.h b/ssh-gss.h +--- a/ssh-gss.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/ssh-gss.h 2024-09-16 11:46:34.710940224 +0200 +@@ -61,10 +61,36 @@ + + #define SSH_GSS_OIDTYPE 0x06 + ++#define SSH2_MSG_KEXGSS_INIT 30 ++#define SSH2_MSG_KEXGSS_CONTINUE 31 ++#define SSH2_MSG_KEXGSS_COMPLETE 32 ++#define SSH2_MSG_KEXGSS_HOSTKEY 33 ++#define SSH2_MSG_KEXGSS_ERROR 34 ++#define SSH2_MSG_KEXGSS_GROUPREQ 40 ++#define SSH2_MSG_KEXGSS_GROUP 41 ++#define KEX_GSS_GRP1_SHA1_ID "gss-group1-sha1-" ++#define KEX_GSS_GRP14_SHA1_ID "gss-group14-sha1-" ++#define KEX_GSS_GRP14_SHA256_ID "gss-group14-sha256-" ++#define KEX_GSS_GRP16_SHA512_ID "gss-group16-sha512-" ++#define KEX_GSS_GEX_SHA1_ID "gss-gex-sha1-" ++#define KEX_GSS_NISTP256_SHA256_ID "gss-nistp256-sha256-" ++#define KEX_GSS_C25519_SHA256_ID "gss-curve25519-sha256-" ++ ++#define GSS_KEX_DEFAULT_KEX \ ++ KEX_GSS_GRP14_SHA256_ID "," \ ++ KEX_GSS_GRP16_SHA512_ID "," \ ++ KEX_GSS_NISTP256_SHA256_ID "," \ ++ KEX_GSS_C25519_SHA256_ID "," \ ++ KEX_GSS_GRP14_SHA1_ID "," \ ++ KEX_GSS_GEX_SHA1_ID ++ ++#include "digest.h" /* SSH_DIGEST_MAX_LENGTH */ ++ + typedef struct { + char *filename; + char *envvar; + char *envval; ++ struct passwd *owner; + void *data; + } ssh_gssapi_ccache; + +@@ -72,8 +98,11 @@ typedef struct { + gss_buffer_desc displayname; + gss_buffer_desc exportedname; + gss_cred_id_t creds; ++ gss_name_t name; + struct ssh_gssapi_mech_struct *mech; + ssh_gssapi_ccache store; ++ int used; ++ int updated; + } ssh_gssapi_client; + + typedef struct ssh_gssapi_mech_struct { +@@ -84,6 +113,7 @@ typedef struct ssh_gssapi_mech_struct { + int (*userok) (ssh_gssapi_client *, char *); + int (*localname) (ssh_gssapi_client *, char **); + void (*storecreds) (ssh_gssapi_client *); ++ int (*updatecreds) (ssh_gssapi_ccache *, ssh_gssapi_client *); + } ssh_gssapi_mech; + + typedef struct { +@@ -94,10 +124,21 @@ typedef struct { + gss_OID oid; /* client */ + gss_cred_id_t creds; /* server */ + gss_name_t client; /* server */ +- gss_cred_id_t client_creds; /* server */ ++ gss_cred_id_t client_creds; /* both */ ++ struct sshbuf *shared_secret; /* both */ ++ struct sshbuf *server_pubkey; /* server */ ++ struct sshbuf *server_blob; /* client */ ++ struct sshbuf *server_host_key_blob; /* client */ ++ gss_buffer_desc msg_tok; /* client */ ++ gss_buffer_desc buf; /* both */ ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; /* both */ ++ size_t hashlen; /* both */ ++ int first; /* client */ ++ BIGNUM *dh_client_pub; /* server (gex) */ + } Gssctxt; + + extern ssh_gssapi_mech *supported_mechs[]; ++extern Gssctxt *gss_kex_context; + + int ssh_gssapi_check_oid(Gssctxt *, void *, size_t); + void ssh_gssapi_set_oid_data(Gssctxt *, void *, size_t); +@@ -108,6 +149,7 @@ OM_uint32 ssh_gssapi_test_oid_supported( + + struct sshbuf; + int ssh_gssapi_get_buffer_desc(struct sshbuf *, gss_buffer_desc *); ++int ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *, gss_buffer_desc *); + + OM_uint32 ssh_gssapi_import_name(Gssctxt *, const char *); + OM_uint32 ssh_gssapi_init_ctx(Gssctxt *, int, +@@ -122,17 +164,33 @@ void ssh_gssapi_delete_ctx(Gssctxt **); + OM_uint32 ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); + void ssh_gssapi_buildmic(struct sshbuf *, const char *, + const char *, const char *, const struct sshbuf *); +-int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *); ++int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *, const char *); ++OM_uint32 ssh_gssapi_client_identity(Gssctxt *, const char *); ++int ssh_gssapi_credentials_updated(Gssctxt *); + + /* In the server */ ++typedef int ssh_gssapi_check_fn(Gssctxt **, gss_OID, const char *, ++ const char *); ++char *ssh_gssapi_client_mechanisms(const char *, const char *, const char *); ++char *ssh_gssapi_kex_mechs(gss_OID_set, ssh_gssapi_check_fn *, const char *, ++ const char *, const char *); ++gss_OID ssh_gssapi_id_kex(Gssctxt *, char *, int); ++int ssh_gssapi_server_check_mech(Gssctxt **,gss_OID, const char *, ++ const char *); + OM_uint32 ssh_gssapi_server_ctx(Gssctxt **, gss_OID); +-int ssh_gssapi_userok(char *name); ++int ssh_gssapi_userok(char *name, struct passwd *, int kex); + OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); + void ssh_gssapi_do_child(char ***, u_int *); + void ssh_gssapi_cleanup_creds(void); + void ssh_gssapi_storecreds(void); + const char *ssh_gssapi_displayname(void); + ++char *ssh_gssapi_server_mechanisms(void); ++int ssh_gssapi_oid_table_ok(void); ++ ++int ssh_gssapi_update_creds(ssh_gssapi_ccache *store); ++void ssh_gssapi_rekey_creds(void); ++ + #endif /* GSSAPI */ + + #endif /* _SSH_GSS_H */ +diff --color -ruNp a/sshkey.c b/sshkey.c +--- a/sshkey.c 2024-07-01 06:36:28.000000000 +0200 ++++ b/sshkey.c 2024-09-16 11:46:34.706940139 +0200 +@@ -131,6 +131,75 @@ extern const struct sshkey_impl sshkey_x + extern const struct sshkey_impl sshkey_xmss_cert_impl; + #endif + ++static int ssh_gss_equal(const struct sshkey *, const struct sshkey *) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_serialize_public(const struct sshkey *, struct sshbuf *, ++ enum sshkey_serialize_rep) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_deserialize_public(const char *, struct sshbuf *, ++ struct sshkey *) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_serialize_private(const struct sshkey *, struct sshbuf *, ++ enum sshkey_serialize_rep) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_deserialize_private(const char *, struct sshbuf *, ++ struct sshkey *) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_copy_public(const struct sshkey *, struct sshkey *) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static int ssh_gss_verify(const struct sshkey *, const u_char *, size_t, ++ const u_char *, size_t, const char *, u_int, ++ struct sshkey_sig_details **) ++{ ++ return SSH_ERR_FEATURE_UNSUPPORTED; ++} ++ ++static const struct sshkey_impl_funcs sshkey_gss_funcs = { ++ /* .size = */ NULL, ++ /* .alloc = */ NULL, ++ /* .cleanup = */ NULL, ++ /* .equal = */ ssh_gss_equal, ++ /* .ssh_serialize_public = */ ssh_gss_serialize_public, ++ /* .ssh_deserialize_public = */ ssh_gss_deserialize_public, ++ /* .ssh_serialize_private = */ ssh_gss_serialize_private, ++ /* .ssh_deserialize_private = */ ssh_gss_deserialize_private, ++ /* .generate = */ NULL, ++ /* .copy_public = */ ssh_gss_copy_public, ++ /* .sign = */ NULL, ++ /* .verify = */ ssh_gss_verify, ++}; ++ ++/* The struct is intentionally dummy and has no gss calls */ ++static const struct sshkey_impl sshkey_gss_kex_impl = { ++ /* .name = */ "null", ++ /* .shortname = */ "null", ++ /* .sigalg = */ NULL, ++ /* .type = */ KEY_NULL, ++ /* .nid = */ 0, ++ /* .cert = */ 0, ++ /* .sigonly = */ 0, ++ /* .keybits = */ 0, /* FIXME */ ++ /* .funcs = */ &sshkey_gss_funcs, ++}; ++ + const struct sshkey_impl * const keyimpls[] = { + &sshkey_ed25519_impl, + &sshkey_ed25519_cert_impl, +@@ -169,6 +238,7 @@ const struct sshkey_impl * const keyimpl + &sshkey_xmss_impl, + &sshkey_xmss_cert_impl, + #endif ++ &sshkey_gss_kex_impl, + NULL + }; + +@@ -324,7 +394,7 @@ sshkey_alg_list(int certs_only, int plai + + for (i = 0; keyimpls[i] != NULL; i++) { + impl = keyimpls[i]; +- if (impl->name == NULL) ++ if (impl->name == NULL || impl->type == KEY_NULL) + continue; + if (!include_sigonly && impl->sigonly) + continue; +diff --color -ruNp a/sshkey.h b/sshkey.h +--- a/sshkey.h 2024-07-01 06:36:28.000000000 +0200 ++++ b/sshkey.h 2024-09-16 11:46:34.706940139 +0200 +@@ -71,6 +71,7 @@ enum sshkey_types { + KEY_ECDSA_SK_CERT, + KEY_ED25519_SK, + KEY_ED25519_SK_CERT, ++ KEY_NULL, + KEY_UNSPEC + }; + diff --git a/openssh-9.6p1-gsskex-new-api.patch b/openssh-9.6p1-gsskex-new-api.patch deleted file mode 100644 index 0f2eabb..0000000 --- a/openssh-9.6p1-gsskex-new-api.patch +++ /dev/null @@ -1,1965 +0,0 @@ -diff --color -ruNp a/gss-genr.c b/gss-genr.c ---- a/gss-genr.c 2024-05-16 15:49:43.999411060 +0200 -+++ b/gss-genr.c 2024-06-26 12:17:55.586856954 +0200 -@@ -346,6 +346,7 @@ ssh_gssapi_build_ctx(Gssctxt **ctx) - (*ctx)->creds = GSS_C_NO_CREDENTIAL; - (*ctx)->client = GSS_C_NO_NAME; - (*ctx)->client_creds = GSS_C_NO_CREDENTIAL; -+ (*ctx)->first = 1; - } - - /* Delete our context, providing it has been built correctly */ -@@ -371,6 +372,12 @@ ssh_gssapi_delete_ctx(Gssctxt **ctx) - gss_release_name(&ms, &(*ctx)->client); - if ((*ctx)->client_creds != GSS_C_NO_CREDENTIAL) - gss_release_cred(&ms, &(*ctx)->client_creds); -+ sshbuf_free((*ctx)->shared_secret); -+ sshbuf_free((*ctx)->server_pubkey); -+ sshbuf_free((*ctx)->server_host_key_blob); -+ sshbuf_free((*ctx)->server_blob); -+ explicit_bzero((*ctx)->hash, sizeof((*ctx)->hash)); -+ BN_clear_free((*ctx)->dh_client_pub); - - free(*ctx); - *ctx = NULL; -diff --color -ruNp a/kexgssc.c b/kexgssc.c ---- a/kexgssc.c 2024-05-16 15:49:43.820407648 +0200 -+++ b/kexgssc.c 2024-07-02 16:26:25.628746744 +0200 -@@ -47,566 +47,658 @@ - - #include "ssh-gss.h" - --int --kexgss_client(struct ssh *ssh) -+static int input_kexgss_hostkey(int, u_int32_t, struct ssh *); -+static int input_kexgss_continue(int, u_int32_t, struct ssh *); -+static int input_kexgss_complete(int, u_int32_t, struct ssh *); -+static int input_kexgss_error(int, u_int32_t, struct ssh *); -+static int input_kexgssgex_group(int, u_int32_t, struct ssh *); -+static int input_kexgssgex_continue(int, u_int32_t, struct ssh *); -+static int input_kexgssgex_complete(int, u_int32_t, struct ssh *); -+ -+static int -+kexgss_final(struct ssh *ssh) - { - struct kex *kex = ssh->kex; -- 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; -+ Gssctxt *gss = kex->gss; - struct sshbuf *empty = NULL; -- u_char *msg; -- int type = 0; -- int first = 1; -+ struct sshbuf *shared_secret = NULL; - 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) { -- ssh_gssapi_delete_ctx(&ctxt); -- 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) { -- u_char *tmp = NULL; -- size_t tmp_len = 0; -- -- debug("Received KEXGSS_HOSTKEY"); -- if (server_host_key_blob) -- fatal("Server host key received more than once"); -- if ((r = sshpkt_get_string(ssh, &tmp, &tmp_len)) != 0) -- fatal("Failed to read server host key: %s", ssh_err(r)); -- if ((server_host_key_blob = sshbuf_from(tmp, tmp_len)) == NULL) -- fatal("sshbuf_from failed"); -- } -- } 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); -+ r = kex_dh_dec(kex, gss->server_blob, &shared_secret); - break; - case KEX_GSS_C25519_SHA256: -- if (sshbuf_ptr(server_blob)[sshbuf_len(server_blob)] & 0x80) -+ if (sshbuf_ptr(gss->server_blob)[sshbuf_len(gss->server_blob)] & 0x80) - fatal("The received key has MSB of last octet set!"); -- r = kex_c25519_dec(kex, server_blob, &shared_secret); -+ r = kex_c25519_dec(kex, gss->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_len(gss->server_blob) != 65) -+ fatal("The received NIST-P256 key did not match " -+ "expected length (expected 65, got %zu)", -+ sshbuf_len(gss->server_blob)); - -- if (sshbuf_ptr(server_blob)[0] != POINT_CONVERSION_UNCOMPRESSED) -+ if (sshbuf_ptr(gss->server_blob)[0] != POINT_CONVERSION_UNCOMPRESSED) - fatal("The received NIST-P256 key does not have first octet 0x04"); - -- r = kex_ecdh_dec(kex, server_blob, &shared_secret); -+ r = kex_ecdh_dec(kex, gss->server_blob, &shared_secret); - break; - default: - r = SSH_ERR_INVALID_ARGUMENT; - break; - } -- if (r != 0) -+ if (r != 0) { -+ ssh_gssapi_delete_ctx(&kex->gss); - goto out; -+ } - - if ((empty = sshbuf_new()) == NULL) { -+ ssh_gssapi_delete_ctx(&kex->gss); - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - - hashlen = sizeof(hash); -- 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) -+ r = kex_gen_hash(kex->hash_alg, kex->client_version, -+ kex->server_version, kex->my, kex->peer, -+ (gss->server_host_key_blob ? gss->server_host_key_blob : empty), -+ kex->client_pub, gss->server_blob, shared_secret, -+ hash, &hashlen); -+ sshbuf_free(empty); -+ if (r != 0) - fatal_f("Unexpected KEX type %d", kex->kex_type); - -- gssbuf.value = hash; -- gssbuf.length = hashlen; -+ gss->buf.value = hash; -+ gss->buf.length = hashlen; - - /* Verify that the hash matches the MIC we just got. */ -- if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) -+ if (GSS_ERROR(ssh_gssapi_checkmic(gss, &gss->buf, &gss->msg_tok))) - sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); - -- gss_release_buffer(&min_status, &msg_tok); -+ gss_release_buffer(&gss->minor, &gss->msg_tok); - - if (kex->gss_deleg_creds) -- ssh_gssapi_credentials_updated(ctxt); -+ ssh_gssapi_credentials_updated(gss); - - if (gss_kex_context == NULL) -- gss_kex_context = ctxt; -+ gss_kex_context = gss; - else -- ssh_gssapi_delete_ctx(&ctxt); -+ ssh_gssapi_delete_ctx(&kex->gss); - - if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) - r = kex_send_newkeys(ssh); - -+ if (kex->gss != NULL) { -+ sshbuf_free(gss->server_host_key_blob); -+ gss->server_host_key_blob = NULL; -+ sshbuf_free(gss->server_blob); -+ gss->server_blob = NULL; -+ } - out: -- explicit_bzero(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); -+ explicit_bzero(hash, sizeof(hash)); - sshbuf_free(shared_secret); - sshbuf_free(kex->client_pub); - kex->client_pub = NULL; - return r; - } - -+static int -+kexgss_init_ctx(struct ssh *ssh, -+ gss_buffer_desc *token_ptr) -+{ -+ struct kex *kex = ssh->kex; -+ Gssctxt *gss = kex->gss; -+ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; -+ OM_uint32 ret_flags; -+ int r; -+ -+ debug("Calling gss_init_sec_context"); -+ -+ gss->major = ssh_gssapi_init_ctx(gss, kex->gss_deleg_creds, -+ token_ptr, &send_tok, &ret_flags); -+ -+ if (GSS_ERROR(gss->major)) { -+ /* XXX Useless code: Missing send? */ -+ if (send_tok.length != 0) { -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || -+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ } -+ fatal("gss_init_context failed"); -+ } -+ -+ /* If we've got an old receive buffer get rid of it */ -+ if (token_ptr != GSS_C_NO_BUFFER) -+ gss_release_buffer(&gss->minor, token_ptr); -+ -+ if (gss->major == GSS_S_COMPLETE) { -+ /* If mutual state flag is not true, kex fails */ -+ if (!(ret_flags & GSS_C_MUTUAL_FLAG)) -+ fatal("Mutual authentication failed"); -+ -+ /* If integ avail flag is not true kex fails */ -+ if (!(ret_flags & GSS_C_INTEG_FLAG)) -+ fatal("Integrity check failed"); -+ } -+ -+ /* -+ * If we have data to send, then the last message that we -+ * received cannot have been a 'complete'. -+ */ -+ if (send_tok.length != 0) { -+ if (gss->first) { -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 || -+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || -+ (r = sshpkt_put_stringb(ssh, kex->client_pub)) != 0) -+ fatal("failed to construct packet: %s", ssh_err(r)); -+ gss->first = 0; -+ } else { -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || -+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) -+ fatal("failed to construct packet: %s", ssh_err(r)); -+ } -+ if ((r = sshpkt_send(ssh)) != 0) -+ fatal("failed to send packet: %s", ssh_err(r)); -+ gss_release_buffer(&gss->minor, &send_tok); -+ -+ /* If we've sent them data, they should reply */ -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, &input_kexgss_hostkey); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgss_continue); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, &input_kexgss_complete); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, &input_kexgss_error); -+ return 0; -+ } -+ /* No data, and not complete */ -+ if (gss->major != GSS_S_COMPLETE) -+ fatal("Not complete, and no token output"); -+ -+ if (gss->major & GSS_S_CONTINUE_NEEDED) -+ return kexgss_init_ctx(ssh, token_ptr); -+ -+ return kexgss_final(ssh); -+} -+ - int --kexgssgex_client(struct ssh *ssh) -+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 *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) -+ ssh_gssapi_build_ctx(&kex->gss); -+ if (ssh_gssapi_id_kex(kex->gss, kex->name, kex->kex_type) == GSS_C_NO_OID) - fatal("Couldn't identify host exchange"); - -- if (ssh_gssapi_import_name(ctxt, kex->gss_host)) -+ if (ssh_gssapi_import_name(kex->gss, kex->gss_host)) - fatal("Couldn't import hostname"); - - if (kex->gss_client && -- ssh_gssapi_client_identity(ctxt, kex->gss_client)) -+ ssh_gssapi_client_identity(kex->gss, kex->gss_client)) - fatal("Couldn't acquire client credentials"); - -- debug("Doing group exchange"); -- nbits = dh_estimate(kex->dh_need * 8); -+ /* Step 1 */ -+ switch (kex->kex_type) { -+ case KEX_GSS_GRP1_SHA1: -+ case KEX_GSS_GRP14_SHA1: -+ case KEX_GSS_GRP14_SHA256: -+ case KEX_GSS_GRP16_SHA512: -+ r = kex_dh_keypair(kex); -+ break; -+ case KEX_GSS_NISTP256_SHA256: -+ r = kex_ecdh_keypair(kex); -+ break; -+ case KEX_GSS_C25519_SHA256: -+ r = kex_c25519_keypair(kex); -+ break; -+ default: -+ fatal_f("Unexpected KEX type %d", kex->kex_type); -+ } -+ if (r != 0) { -+ ssh_gssapi_delete_ctx(&kex->gss); -+ return r; -+ } -+ return kexgss_init_ctx(ssh, GSS_C_NO_BUFFER); -+} - -- 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)); -+static int -+input_kexgss_hostkey(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ u_char *tmp = NULL; -+ size_t tmp_len = 0; -+ int r; -+ -+ debug("Received KEXGSS_HOSTKEY"); -+ if (gss->server_host_key_blob) -+ fatal("Server host key received more than once"); -+ if ((r = sshpkt_get_string(ssh, &tmp, &tmp_len)) != 0) -+ fatal("Failed to read server host key: %s", ssh_err(r)); -+ if ((gss->server_host_key_blob = sshbuf_from(tmp, tmp_len)) == NULL) -+ fatal("sshbuf_from failed"); -+ return 0; -+} - -- if ((r = ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0) -- fatal("Error: %s", ssh_err(r)); -+static int -+input_kexgss_continue(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; -+ int r; - -- if ((r = sshpkt_get_bignum2(ssh, &p)) != 0 || -- (r = sshpkt_get_bignum2(ssh, &g)) != 0 || -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); -+ -+ debug("Received GSSAPI_CONTINUE"); -+ if (gss->major == GSS_S_COMPLETE) -+ fatal("GSSAPI Continue received from server when complete"); -+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || - (r = sshpkt_get_end(ssh)) != 0) -- fatal("shpkt_get_bignum2 failed: %s", ssh_err(r)); -+ fatal("Failed to read token: %s", ssh_err(r)); -+ if (!(gss->major & GSS_S_CONTINUE_NEEDED)) -+ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); -+ return kexgss_init_ctx(ssh, &recv_tok); -+} - -- 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); -+static int -+input_kexgss_complete(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; -+ u_char c; -+ int r; - -- if ((kex->dh = dh_new_group(g, p)) == NULL) -- fatal("dn_new_group() failed"); -- p = g = NULL; /* belong to kex->dh now */ -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); -+ -+ debug("Received GSSAPI_COMPLETE"); -+ if (gss->msg_tok.value != NULL) -+ fatal("Received GSSAPI_COMPLETE twice?"); -+ if ((r = sshpkt_getb_froms(ssh, &gss->server_blob)) != 0 || -+ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &gss->msg_tok)) != 0) -+ fatal("Failed to read message: %s", ssh_err(r)); -+ -+ /* Is there a token included? */ -+ if ((r = sshpkt_get_u8(ssh, &c)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ if (c) { -+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0) -+ fatal("Failed to read token: %s", ssh_err(r)); -+ /* If we're already complete - protocol error */ -+ if (gss->major == GSS_S_COMPLETE) -+ sshpkt_disconnect(ssh, "Protocol error: received token when complete"); -+ } else { -+ if (gss->major != GSS_S_COMPLETE) -+ sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); -+ } -+ if ((r = sshpkt_get_end(ssh)) != 0) -+ fatal("Expecting end of packet."); - -- if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) -- goto out; -- DH_get0_key(kex->dh, &pub_key, NULL); -+ if (gss->major & GSS_S_CONTINUE_NEEDED) -+ return kexgss_init_ctx(ssh, &recv_tok); - -- token_ptr = GSS_C_NO_BUFFER; -+ return kexgss_final(ssh); -+} - -- 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"); -- } -+static int -+input_kexgss_error(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ u_char *msg; -+ int r; - -- /* 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"); -- } -+ debug("Received Error"); -+ if ((r = sshpkt_get_u32(ssh, &gss->major)) != 0 || -+ (r = sshpkt_get_u32(ssh, &gss->minor)) != 0 || -+ (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 || -+ (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */ -+ (r = sshpkt_get_end(ssh)) != 0) -+ fatal("sshpkt_get failed: %s", ssh_err(r)); -+ fatal("GSSAPI Error: \n%.400s", msg); -+ return 0; -+} - -- /* -- * 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) { -- u_char *tmp = NULL; -- size_t tmp_len = 0; -- -- debug("Received KEXGSS_HOSTKEY"); -- if (server_host_key_blob) -- fatal("Server host key received more than once"); -- if ((r = sshpkt_get_string(ssh, &tmp, &tmp_len)) != 0) -- fatal("sshpkt failed: %s", ssh_err(r)); -- if ((server_host_key_blob = sshbuf_from(tmp, tmp_len)) == NULL) -- fatal("sshbuf_from failed"); -- } -- } 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); -+/*******************************************************/ -+/******************** KEXGSSGEX ************************/ -+/*******************************************************/ -+ -+int -+kexgssgex_client(struct ssh *ssh) -+{ -+ struct kex *kex = ssh->kex; -+ int r; -+ -+ /* Initialise our GSSAPI world */ -+ ssh_gssapi_build_ctx(&kex->gss); -+ if (ssh_gssapi_id_kex(kex->gss, kex->name, kex->kex_type) == GSS_C_NO_OID) -+ fatal("Couldn't identify host exchange"); -+ -+ if (ssh_gssapi_import_name(kex->gss, kex->gss_host)) -+ fatal("Couldn't import hostname"); -+ -+ if (kex->gss_client && -+ ssh_gssapi_client_identity(kex->gss, kex->gss_client)) -+ fatal("Couldn't acquire client credentials"); -+ -+ debug("Doing group exchange"); -+ kex->min = DH_GRP_MIN; -+ kex->max = DH_GRP_MAX; -+ kex->nbits = dh_estimate(kex->dh_need * 8); -+ -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUPREQ)) != 0 || -+ (r = sshpkt_put_u32(ssh, kex->min)) != 0 || -+ (r = sshpkt_put_u32(ssh, kex->nbits)) != 0 || -+ (r = sshpkt_put_u32(ssh, kex->max)) != 0 || -+ (r = sshpkt_send(ssh)) != 0) -+ fatal("Failed to construct a packet: %s", ssh_err(r)); -+ -+ debug("Wait SSH2_MSG_KEXGSS_GROUP"); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUP, &input_kexgssgex_group); -+ return 0; -+} -+ -+static int -+kexgssgex_final(struct ssh *ssh) -+{ -+ struct kex *kex = ssh->kex; -+ Gssctxt *gss = kex->gss; -+ struct sshbuf *buf = NULL; -+ struct sshbuf *empty = NULL; -+ struct sshbuf *shared_secret = NULL; -+ BIGNUM *dh_server_pub = NULL; -+ const BIGNUM *pub_key, *dh_p, *dh_g; -+ u_char hash[SSH_DIGEST_MAX_LENGTH]; -+ size_t hashlen; -+ int r = SSH_ERR_INTERNAL_ERROR; - - /* - * We _must_ have received a COMPLETE message in reply from the -- * server, which will have set dh_server_pub and msg_tok -+ * 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"); -- - /* 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) -+ (r = sshbuf_put_stringb(buf, gss->server_blob)) != 0 || -+ (r = sshbuf_get_bignum2(buf, &dh_server_pub)) != 0) { -+ ssh_gssapi_delete_ctx(&kex->gss); - goto out; -+ } - sshbuf_free(buf); - buf = NULL; - - if ((shared_secret = sshbuf_new()) == NULL) { -+ ssh_gssapi_delete_ctx(&kex->gss); - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - -- if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0) -+ if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0) { -+ ssh_gssapi_delete_ctx(&kex->gss); - goto out; -+ } -+ - if ((empty = sshbuf_new()) == NULL) { -+ ssh_gssapi_delete_ctx(&kex->gss); - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - -+ DH_get0_key(kex->dh, &pub_key, NULL); - DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); - hashlen = sizeof(hash); -- 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) -+ r = kexgex_hash(kex->hash_alg, kex->client_version, -+ kex->server_version, kex->my, kex->peer, -+ (gss->server_host_key_blob ? gss->server_host_key_blob : empty), -+ kex->min, kex->nbits, kex->max, dh_p, dh_g, pub_key, -+ dh_server_pub, sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), -+ hash, &hashlen); -+ sshbuf_free(empty); -+ if (r != 0) - fatal("Failed to calculate hash: %s", ssh_err(r)); - -- gssbuf.value = hash; -- gssbuf.length = hashlen; -+ gss->buf.value = hash; -+ gss->buf.length = hashlen; - - /* Verify that the hash matches the MIC we just got. */ -- if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) -+ if (GSS_ERROR(ssh_gssapi_checkmic(gss, &gss->buf, &gss->msg_tok))) - sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); - -- gss_release_buffer(&min_status, &msg_tok); -+ gss_release_buffer(&gss->minor, &gss->msg_tok); - - if (kex->gss_deleg_creds) -- ssh_gssapi_credentials_updated(ctxt); -+ ssh_gssapi_credentials_updated(gss); - - if (gss_kex_context == NULL) -- gss_kex_context = ctxt; -+ gss_kex_context = gss; - else -- ssh_gssapi_delete_ctx(&ctxt); -+ ssh_gssapi_delete_ctx(&kex->gss); - - /* Finally derive the keys and send them */ - if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) - r = kex_send_newkeys(ssh); -+ -+ if (kex->gss != NULL) { -+ sshbuf_free(gss->server_host_key_blob); -+ gss->server_host_key_blob = NULL; -+ sshbuf_free(gss->server_blob); -+ gss->server_blob = NULL; -+ } - out: -- 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; - } - -+static int -+kexgssgex_init_ctx(struct ssh *ssh, -+ gss_buffer_desc *token_ptr) -+{ -+ struct kex *kex = ssh->kex; -+ Gssctxt *gss = kex->gss; -+ const BIGNUM *pub_key; -+ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; -+ OM_uint32 ret_flags; -+ int r; -+ -+ /* Step 2 - call GSS_Init_sec_context() */ -+ debug("Calling gss_init_sec_context"); -+ -+ gss->major = ssh_gssapi_init_ctx(gss, kex->gss_deleg_creds, -+ token_ptr, &send_tok, &ret_flags); -+ -+ if (GSS_ERROR(gss->major)) { -+ /* XXX Useless code: Missing send? */ -+ if (send_tok.length != 0) { -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || -+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ } -+ fatal("gss_init_context failed"); -+ } -+ -+ /* If we've got an old receive buffer get rid of it */ -+ if (token_ptr != GSS_C_NO_BUFFER) -+ gss_release_buffer(&gss->minor, token_ptr); -+ -+ if (gss->major == GSS_S_COMPLETE) { -+ /* If mutual state flag is not true, kex fails */ -+ if (!(ret_flags & GSS_C_MUTUAL_FLAG)) -+ fatal("Mutual authentication failed"); -+ -+ /* If integ avail flag is not true kex fails */ -+ if (!(ret_flags & GSS_C_INTEG_FLAG)) -+ fatal("Integrity check failed"); -+ } -+ -+ /* -+ * If we have data to send, then the last message that we -+ * received cannot have been a 'complete'. -+ */ -+ if (send_tok.length != 0) { -+ if (gss->first) { -+ DH_get0_key(kex->dh, &pub_key, NULL); -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 || -+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || -+ (r = sshpkt_put_bignum2(ssh, pub_key)) != 0) -+ fatal("failed to construct packet: %s", ssh_err(r)); -+ gss->first = 0; -+ } else { -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || -+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) -+ fatal("failed to construct packet: %s", ssh_err(r)); -+ } -+ if ((r = sshpkt_send(ssh)) != 0) -+ fatal("failed to send packet: %s", ssh_err(r)); -+ gss_release_buffer(&gss->minor, &send_tok); -+ -+ /* If we've sent them data, they should reply */ -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, &input_kexgss_hostkey); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgssgex_continue); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, &input_kexgssgex_complete); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, &input_kexgss_error); -+ return 0; -+ } -+ /* No data, and not complete */ -+ if (gss->major != GSS_S_COMPLETE) -+ fatal("Not complete, and no token output"); -+ -+ if (gss->major & GSS_S_CONTINUE_NEEDED) -+ return kexgssgex_init_ctx(ssh, token_ptr); -+ -+ return kexgssgex_final(ssh); -+} -+ -+static int -+input_kexgssgex_group(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ struct kex *kex = ssh->kex; -+ BIGNUM *p = NULL; -+ BIGNUM *g = NULL; -+ int r; -+ -+ debug("Received SSH2_MSG_KEXGSS_GROUP"); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUP, NULL); -+ -+ if ((r = sshpkt_get_bignum2(ssh, &p)) != 0 || -+ (r = sshpkt_get_bignum2(ssh, &g)) != 0 || -+ (r = sshpkt_get_end(ssh)) != 0) -+ fatal("shpkt_get_bignum2 failed: %s", ssh_err(r)); -+ -+ if (BN_num_bits(p) < kex->min || BN_num_bits(p) > kex->max) -+ fatal("GSSGRP_GEX group out of range: %d !< %d !< %d", -+ kex->min, BN_num_bits(p), kex->max); -+ -+ if ((kex->dh = dh_new_group(g, p)) == NULL) -+ fatal("dn_new_group() failed"); -+ p = g = NULL; /* belong to kex->dh now */ -+ -+ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) { -+ ssh_gssapi_delete_ctx(&kex->gss); -+ DH_free(kex->dh); -+ kex->dh = NULL; -+ return r; -+ } -+ -+ return kexgssgex_init_ctx(ssh, GSS_C_NO_BUFFER); -+} -+ -+static int -+input_kexgssgex_continue(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; -+ int r; -+ -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); -+ -+ debug("Received GSSAPI_CONTINUE"); -+ if (gss->major == GSS_S_COMPLETE) -+ fatal("GSSAPI Continue received from server when complete"); -+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || -+ (r = sshpkt_get_end(ssh)) != 0) -+ fatal("Failed to read token: %s", ssh_err(r)); -+ if (!(gss->major & GSS_S_CONTINUE_NEEDED)) -+ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); -+ return kexgssgex_init_ctx(ssh, &recv_tok); -+} -+ -+static int -+input_kexgssgex_complete(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER; -+ u_char c; -+ int r; -+ -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL); -+ -+ debug("Received GSSAPI_COMPLETE"); -+ if (gss->msg_tok.value != NULL) -+ fatal("Received GSSAPI_COMPLETE twice?"); -+ if ((r = sshpkt_getb_froms(ssh, &gss->server_blob)) != 0 || -+ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &gss->msg_tok)) != 0) -+ fatal("Failed to read message: %s", ssh_err(r)); -+ -+ /* Is there a token included? */ -+ if ((r = sshpkt_get_u8(ssh, &c)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ if (c) { -+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0) -+ fatal("Failed to read token: %s", ssh_err(r)); -+ /* If we're already complete - protocol error */ -+ if (gss->major == GSS_S_COMPLETE) -+ sshpkt_disconnect(ssh, "Protocol error: received token when complete"); -+ } else { -+ if (gss->major != GSS_S_COMPLETE) -+ sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); -+ } -+ if ((r = sshpkt_get_end(ssh)) != 0) -+ fatal("Expecting end of packet."); -+ -+ if (gss->major & GSS_S_CONTINUE_NEEDED) -+ return kexgssgex_init_ctx(ssh, &recv_tok); -+ -+ return kexgssgex_final(ssh); -+} -+ - #endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ -diff --color -ruNp a/kexgsss.c b/kexgsss.c ---- a/kexgsss.c 2024-05-16 15:49:43.820407648 +0200 -+++ b/kexgsss.c 2024-07-02 16:29:05.744790839 +0200 -@@ -50,33 +50,18 @@ - - extern ServerOptions options; - -+static int input_kexgss_init(int, u_int32_t, struct ssh *); -+static int input_kexgss_continue(int, u_int32_t, struct ssh *); -+static int input_kexgssgex_groupreq(int, u_int32_t, struct ssh *); -+static int input_kexgssgex_init(int, u_int32_t, struct ssh *); -+static int input_kexgssgex_continue(int, u_int32_t, struct ssh *); -+ - int - kexgss_server(struct ssh *ssh) - { - struct kex *kex = ssh->kex; -- 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 = {0, NULL}, 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 */ - -@@ -92,135 +77,91 @@ kexgss_server(struct ssh *ssh) - 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"); -+ fatal("Unknown gssapi mechanism"); - - debug2_f("Acquiring credentials"); - -- if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&ctxt, oid))) -+ if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, 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 (gssbuf.value != 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)); -+ ssh_gssapi_build_ctx(&kex->gss); -+ if (kex->gss == NULL) -+ fatal("Unable to allocate memory for gss context"); -+ -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, &input_kexgss_init); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgss_continue); -+ debug("Wait SSH2_MSG_KEXGSS_INIT"); -+ return 0; -+} - -- 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 */ -- -- /* Calculate the hash early so we can free the -- * client_pubkey, which has reference to the parent -- * buffer state->incoming_packet -- */ -- 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; -- -- sshbuf_free(client_pubkey); -- client_pubkey = NULL; -- -- 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); -- } -+static inline void -+kexgss_accept_ctx(struct ssh *ssh, -+ gss_buffer_desc *recv_tok, -+ gss_buffer_desc *send_tok, -+ OM_uint32 *ret_flags) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ int r; - -- maj_status = mm_ssh_gssapi_accept_ctx(ctxt, &recv_tok, -- &send_tok, &ret_flags); -+ gss->major = mm_ssh_gssapi_accept_ctx(gss, recv_tok, send_tok, ret_flags); -+ gss_release_buffer(&gss->minor, recv_tok); - -- gss_release_buffer(&min_status, &recv_tok); -+ if (gss->major != GSS_S_COMPLETE && send_tok->length == 0) -+ fatal("Zero length token output when incomplete"); - -- if (maj_status != GSS_S_COMPLETE && send_tok.length == 0) -- fatal("Zero length token output when incomplete"); -+ if (gss->buf.value == NULL) -+ fatal("No client public key"); - -- if (gssbuf.value == NULL) -- fatal("No client public key"); -+ if (gss->major & GSS_S_CONTINUE_NEEDED) { -+ debug("Sending GSSAPI_CONTINUE"); -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || -+ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 || -+ (r = sshpkt_send(ssh)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ gss_release_buffer(&gss->minor, send_tok); -+ } -+} - -- 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); -+static inline int -+kexgss_final(struct ssh *ssh, -+ gss_buffer_desc *send_tok, -+ OM_uint32 *ret_flags) -+{ -+ struct kex *kex = ssh->kex; -+ Gssctxt *gss = kex->gss; -+ gss_buffer_desc msg_tok; -+ int r; -+ -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); - -- if (GSS_ERROR(maj_status)) { -- if (send_tok.length > 0) { -+ if (GSS_ERROR(gss->major)) { -+ if (send_tok->length > 0) { - if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || -- (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || -+ (r = sshpkt_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)) -+ if (!(*ret_flags & GSS_C_MUTUAL_FLAG)) - fatal("Mutual Authentication flag wasn't set"); - -- if (!(ret_flags & GSS_C_INTEG_FLAG)) -+ if (!(*ret_flags & GSS_C_INTEG_FLAG)) - fatal("Integrity flag wasn't set"); - -- if (GSS_ERROR(mm_ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok))) -+ if (GSS_ERROR(mm_ssh_gssapi_sign(gss, &gss->buf, &msg_tok))) - fatal("Couldn't get MIC"); - - if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 || -- (r = sshpkt_put_stringb(ssh, server_pubkey)) != 0 || -+ (r = sshpkt_put_stringb(ssh, gss->server_pubkey)) != 0 || - (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0) - fatal("sshpkt failed: %s", ssh_err(r)); - -- if (send_tok.length != 0) { -+ if (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) -+ (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 */ -@@ -229,59 +170,139 @@ kexgss_server(struct ssh *ssh) - 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); -+ gss_release_buffer(&gss->minor, send_tok); -+ gss_release_buffer(&gss->minor, &msg_tok); - - if (gss_kex_context == NULL) -- gss_kex_context = ctxt; -+ gss_kex_context = gss; - else -- ssh_gssapi_delete_ctx(&ctxt); -+ ssh_gssapi_delete_ctx(&kex->gss); - -- if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) -+ if ((r = kex_derive_keys(ssh, gss->hash, gss->hashlen, gss->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); -+ -+ if (kex->gss != NULL) { -+ explicit_bzero(gss->hash, sizeof(gss->hash)); -+ sshbuf_free(gss->shared_secret); -+ gss->shared_secret = NULL; -+ sshbuf_free(gss->server_pubkey); -+ gss->server_pubkey = NULL; -+ } - return r; - } - --int --kexgssgex_server(struct ssh *ssh) -+static int -+input_kexgss_init(int type, -+ u_int32_t seq, -+ struct ssh *ssh) - { - struct kex *kex = ssh->kex; -- OM_uint32 maj_status, min_status; -+ Gssctxt *gss = kex->gss; -+ struct sshbuf *empty; -+ struct sshbuf *client_pubkey = NULL; -+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; -+ OM_uint32 ret_flags = 0; -+ int r; -+ -+ debug("SSH2_MSG_KEXGSS_INIT received"); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); - -- /* -- * 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. -+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || -+ (r = sshpkt_getb_froms(ssh, &client_pubkey)) != 0 || -+ (r = sshpkt_get_end(ssh)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ -+ switch (kex->kex_type) { -+ case KEX_GSS_GRP1_SHA1: -+ case KEX_GSS_GRP14_SHA1: -+ case KEX_GSS_GRP14_SHA256: -+ case KEX_GSS_GRP16_SHA512: -+ r = kex_dh_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret); -+ break; -+ case KEX_GSS_NISTP256_SHA256: -+ r = kex_ecdh_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret); -+ break; -+ case KEX_GSS_C25519_SHA256: -+ r = kex_c25519_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret); -+ break; -+ default: -+ fatal_f("Unexpected KEX type %d", kex->kex_type); -+ } -+ if (r != 0) { -+ sshbuf_free(client_pubkey); -+ ssh_gssapi_delete_ctx(&kex->gss); -+ return r; -+ } -+ -+ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ -+ -+ if ((empty = sshbuf_new()) == NULL) { -+ sshbuf_free(client_pubkey); -+ ssh_gssapi_delete_ctx(&kex->gss); -+ return SSH_ERR_ALLOC_FAIL; -+ } -+ -+ /* Calculate the hash early so we can free the -+ * client_pubkey, which has reference to the parent -+ * buffer state->incoming_packet - */ -+ gss->hashlen = sizeof(gss->hash); -+ r = kex_gen_hash(kex->hash_alg, kex->client_version, kex->server_version, -+ kex->peer, kex->my, empty, client_pubkey, gss->server_pubkey, -+ gss->shared_secret, gss->hash, &gss->hashlen); -+ sshbuf_free(empty); -+ sshbuf_free(client_pubkey); -+ if (r != 0) { -+ ssh_gssapi_delete_ctx(&kex->gss); -+ return r; -+ } -+ -+ gss->buf.value = gss->hash; -+ gss->buf.length = gss->hashlen; -+ -+ kexgss_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); -+ if (gss->major & GSS_S_CONTINUE_NEEDED) -+ return 0; - -+ return kexgss_final(ssh, &send_tok, &ret_flags); -+} -+ -+static int -+input_kexgss_continue(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; - OM_uint32 ret_flags = 0; -- 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; -+ int r; -+ -+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || -+ (r = sshpkt_get_end(ssh)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ -+ kexgss_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); -+ if (gss->major & GSS_S_CONTINUE_NEEDED) -+ return 0; -+ -+ return kexgss_final(ssh, &send_tok, &ret_flags); -+} -+ -+/*******************************************************/ -+/******************** KEXGSSGEX ************************/ -+/*******************************************************/ -+ -+int -+kexgssgex_server(struct ssh *ssh) -+{ -+ struct kex *kex = ssh->kex; - gss_OID oid; - char *mechs; -- 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 */ - -@@ -289,153 +310,125 @@ kexgssgex_server(struct ssh *ssh) - * 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); -+ 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"); -+ fatal("Unknown gssapi mechanism"); - - debug2_f("Acquiring credentials"); - -- if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&ctxt, oid))) -+ if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, 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 = mm_choose_dh(min, nbits, max); -- if (kex->dh == NULL) { -- sshpkt_disconnect(ssh, "Protocol error: no matching group found"); -- fatal("Protocol error: no matching group found"); -- } -+ ssh_gssapi_build_ctx(&kex->gss); -+ if (kex->gss == NULL) -+ fatal("Unable to allocate memory for gss context"); - -- 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; -+ debug("Doing group exchange"); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUPREQ, &input_kexgssgex_groupreq); -+ return 0; -+} - -- 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)); -+static inline void -+kexgssgex_accept_ctx(struct ssh *ssh, -+ gss_buffer_desc *recv_tok, -+ gss_buffer_desc *send_tok, -+ OM_uint32 *ret_flags) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ int r; - -- /* 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); -- } -+ gss->major = mm_ssh_gssapi_accept_ctx(gss, recv_tok, send_tok, ret_flags); -+ gss_release_buffer(&gss->minor, recv_tok); - -- maj_status = mm_ssh_gssapi_accept_ctx(ctxt, &recv_tok, -- &send_tok, &ret_flags); -+ if (gss->major != GSS_S_COMPLETE && send_tok->length == 0) -+ fatal("Zero length token output when incomplete"); - -- gss_release_buffer(&min_status, &recv_tok); -+ if (gss->dh_client_pub == NULL) -+ fatal("No client public key"); - -- if (maj_status != GSS_S_COMPLETE && send_tok.length == 0) -- fatal("Zero length token output when incomplete"); -+ if (gss->major & GSS_S_CONTINUE_NEEDED) { -+ debug("Sending GSSAPI_CONTINUE"); -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || -+ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 || -+ (r = sshpkt_send(ssh)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ gss_release_buffer(&gss->minor, send_tok); -+ } -+} - -- if (dh_client_pub == NULL) -- fatal("No client public key"); -+static inline int -+kexgssgex_final(struct ssh *ssh, -+ gss_buffer_desc *send_tok, -+ OM_uint32 *ret_flags) -+{ -+ struct kex *kex = ssh->kex; -+ Gssctxt *gss = kex->gss; -+ gss_buffer_desc msg_tok; -+ u_char hash[SSH_DIGEST_MAX_LENGTH]; -+ size_t hashlen; -+ const BIGNUM *pub_key, *dh_p, *dh_g; -+ struct sshbuf *shared_secret = NULL; -+ struct sshbuf *empty = NULL; -+ int r; - -- 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); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL); - -- if (GSS_ERROR(maj_status)) { -- if (send_tok.length > 0) { -+ if (GSS_ERROR(gss->major)) { -+ if (send_tok->length > 0) { - if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || -- (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || -+ (r = sshpkt_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)) -+ if (!(*ret_flags & GSS_C_MUTUAL_FLAG)) - fatal("Mutual Authentication flag wasn't set"); - -- if (!(ret_flags & GSS_C_INTEG_FLAG)) -+ if (!(*ret_flags & GSS_C_INTEG_FLAG)) - fatal("Integrity flag wasn't set"); - - /* calculate shared secret */ -- if ((shared_secret = sshbuf_new()) == NULL) { -+ shared_secret = sshbuf_new(); -+ if (shared_secret == NULL) { -+ ssh_gssapi_delete_ctx(&kex->gss); - r = SSH_ERR_ALLOC_FAIL; - goto out; - } -- if ((r = kex_dh_compute_key(kex, dh_client_pub, shared_secret)) != 0) -+ if ((r = kex_dh_compute_key(kex, gss->dh_client_pub, shared_secret)) != 0) { -+ ssh_gssapi_delete_ctx(&kex->gss); - goto out; -+ } -+ -+ if ((empty = sshbuf_new()) == NULL) { -+ ssh_gssapi_delete_ctx(&kex->gss); -+ r = SSH_ERR_ALLOC_FAIL; -+ goto out; -+ } - - DH_get0_key(kex->dh, &pub_key, NULL); - DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); - hashlen = sizeof(hash); -- 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) -+ r = kexgex_hash(kex->hash_alg, kex->client_version, kex->server_version, -+ kex->peer, kex->my, empty, kex->min, kex->nbits, kex->max, dh_p, dh_g, -+ gss->dh_client_pub, pub_key, sshbuf_ptr(shared_secret), -+ sshbuf_len(shared_secret), hash, &hashlen); -+ sshbuf_free(empty); -+ if (r != 0) - fatal("kexgex_hash failed: %s", ssh_err(r)); - -- gssbuf.value = hash; -- gssbuf.length = hashlen; -+ gss->buf.value = hash; -+ gss->buf.length = hashlen; - -- if (GSS_ERROR(mm_ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok))) -+ if (GSS_ERROR(mm_ssh_gssapi_sign(gss, &gss->buf, &msg_tok))) - fatal("Couldn't get MIC"); - - if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 || -@@ -443,24 +436,24 @@ kexgssgex_server(struct ssh *ssh) - (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 (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) -+ (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)); -+ fatal("sshpkt_send failed: %s", ssh_err(r)); - -- gss_release_buffer(&min_status, &send_tok); -- gss_release_buffer(&min_status, &msg_tok); -+ gss_release_buffer(&gss->minor, send_tok); -+ gss_release_buffer(&gss->minor, &msg_tok); - - if (gss_kex_context == NULL) -- gss_kex_context = ctxt; -+ gss_kex_context = gss; - else -- ssh_gssapi_delete_ctx(&ctxt); -+ ssh_gssapi_delete_ctx(&kex->gss); - - /* Finally derive the keys and send them */ - if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) -@@ -470,13 +463,128 @@ kexgssgex_server(struct ssh *ssh) - * just exchanged. */ - if (options.gss_store_rekey) - ssh_gssapi_rekey_creds(); -+ -+ if (kex->gss != NULL) -+ BN_clear_free(gss->dh_client_pub); -+ - 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; - } -+ -+static int -+input_kexgssgex_groupreq(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ struct kex *kex = ssh->kex; -+ const BIGNUM *dh_p, *dh_g; -+ int min = -1, max = -1, nbits = -1; -+ int cmin = -1, cmax = -1; /* client proposal */ -+ int r; -+ -+ /* 5. S generates an ephemeral key pair (do the allocations early) */ -+ -+ debug("SSH2_MSG_KEXGSS_GROUPREQ received"); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUPREQ, NULL); -+ -+ /* store client proposal to provide valid signature */ -+ if ((r = sshpkt_get_u32(ssh, &cmin)) != 0 || -+ (r = sshpkt_get_u32(ssh, &nbits)) != 0 || -+ (r = sshpkt_get_u32(ssh, &cmax)) != 0 || -+ (r = sshpkt_get_end(ssh)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ -+ kex->nbits = nbits; -+ kex->min = cmin; -+ kex->max = cmax; -+ min = MAX(DH_GRP_MIN, cmin); -+ max = MIN(DH_GRP_MAX, cmax); -+ nbits = MAXIMUM(DH_GRP_MIN, nbits); -+ nbits = MINIMUM(DH_GRP_MAX, nbits); -+ -+ if (max < min || nbits < min || max < nbits) -+ fatal("GSS_GEX, bad parameters: %d !< %d !< %d", min, nbits, max); -+ -+ kex->dh = mm_choose_dh(min, nbits, max); -+ if (kex->dh == NULL) { -+ sshpkt_disconnect(ssh, "Protocol error: no matching group found"); -+ fatal("Protocol error: no matching group found"); -+ } -+ -+ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); -+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 || -+ (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 || -+ (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 || -+ (r = sshpkt_send(ssh)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ -+ if ((r = ssh_packet_write_wait(ssh)) != 0) -+ fatal("ssh_packet_write_wait: %s", ssh_err(r)); -+ -+ /* Compute our exchange value in parallel with the client */ -+ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) { -+ ssh_gssapi_delete_ctx(&kex->gss); -+ DH_free(kex->dh); -+ kex->dh = NULL; -+ return r; -+ } -+ -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, &input_kexgssgex_init); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgssgex_continue); -+ debug("Wait SSH2_MSG_KEXGSS_INIT"); -+ return 0; -+} -+ -+static int -+input_kexgssgex_init(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; -+ OM_uint32 ret_flags = 0; -+ int r; -+ -+ debug("SSH2_MSG_KEXGSS_INIT received"); -+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL); -+ -+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || -+ (r = sshpkt_get_bignum2(ssh, &gss->dh_client_pub)) != 0 || -+ (r = sshpkt_get_end(ssh)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ -+ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ -+ -+ kexgssgex_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); -+ if (gss->major & GSS_S_CONTINUE_NEEDED) -+ return 0; -+ -+ return kexgssgex_final(ssh, &send_tok, &ret_flags); -+} -+ -+static int -+input_kexgssgex_continue(int type, -+ u_int32_t seq, -+ struct ssh *ssh) -+{ -+ Gssctxt *gss = ssh->kex->gss; -+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER; -+ OM_uint32 ret_flags = 0; -+ int r; -+ -+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 || -+ (r = sshpkt_get_end(ssh)) != 0) -+ fatal("sshpkt failed: %s", ssh_err(r)); -+ -+ kexgssgex_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags); -+ if (gss->major & GSS_S_CONTINUE_NEEDED) -+ return 0; -+ -+ return kexgssgex_final(ssh, &send_tok, &ret_flags); -+} -+ - #endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ -diff --color -ruNp a/kex.h b/kex.h ---- a/kex.h 2024-05-16 15:49:43.986410812 +0200 -+++ b/kex.h 2024-06-18 12:19:48.580347469 +0200 -@@ -29,6 +29,10 @@ - #include "mac.h" - #include "crypto_api.h" - -+#ifdef GSSAPI -+# include "ssh-gss.h" /* Gssctxt */ -+#endif -+ - #ifdef WITH_OPENSSL - # include - # include -@@ -177,6 +181,7 @@ struct kex { - int hash_alg; - int ec_nid; - #ifdef GSSAPI -+ Gssctxt *gss; - int gss_deleg_creds; - int gss_trust_dns; - char *gss_host; -diff --color -ruNp a/ssh-gss.h b/ssh-gss.h ---- a/ssh-gss.h 2024-05-16 15:49:43.837407972 +0200 -+++ b/ssh-gss.h 2024-06-27 14:12:48.659866937 +0200 -@@ -88,6 +88,8 @@ extern char **k5users_allowed_cmds; - KEX_GSS_GRP14_SHA1_ID "," \ - KEX_GSS_GEX_SHA1_ID - -+#include "digest.h" /* SSH_DIGEST_MAX_LENGTH */ -+ - typedef struct { - char *filename; - char *envvar; -@@ -127,6 +129,16 @@ typedef struct { - gss_cred_id_t creds; /* server */ - gss_name_t client; /* server */ - gss_cred_id_t client_creds; /* both */ -+ struct sshbuf *shared_secret; /* both */ -+ struct sshbuf *server_pubkey; /* server */ -+ struct sshbuf *server_blob; /* client */ -+ struct sshbuf *server_host_key_blob; /* client */ -+ gss_buffer_desc msg_tok; /* client */ -+ gss_buffer_desc buf; /* both */ -+ u_char hash[SSH_DIGEST_MAX_LENGTH]; /* both */ -+ size_t hashlen; /* both */ -+ int first; /* client */ -+ BIGNUM *dh_client_pub; /* server (gex) */ - } Gssctxt; - - extern ssh_gssapi_mech *supported_mechs[]; diff --git a/openssh.spec b/openssh.spec index 7bf36e2..513354f 100644 --- a/openssh.spec +++ b/openssh.spec @@ -38,8 +38,8 @@ # rpm -ba|--rebuild --define "static_openssl 1" %{?static_openssl:%global static_libcrypto 1} -%global openssh_ver 9.8p1 -%global openssh_rel 6 +%global openssh_ver 9.9p1 +%global openssh_rel 1 Summary: An open source implementation of SSH protocol version 2 Name: openssh @@ -113,7 +113,7 @@ Patch711: openssh-7.8p1-UsePAM-warning.patch # Reenable MONITOR_REQ_GSSCHECKMIC after gssapi-with-mic failures # upstream MR: # https://github.com/openssh-gsskex/openssh-gsskex/pull/21 -Patch800: openssh-8.0p1-gssapi-keyex.patch +Patch800: openssh-9.6p1-gssapi-keyex.patch #http://www.mail-archive.com/kerberos@mit.edu/msg17591.html Patch801: openssh-6.6p1-force_krb.patch # add new option GSSAPIEnablek5users and disable using ~/.k5users by default (#1169843) @@ -124,8 +124,6 @@ Patch802: openssh-6.6p1-GSSAPIEnablek5users.patch Patch804: openssh-7.7p1-gssapi-new-unique.patch # Respect k5login_directory option in krk5.conf (#1328243) Patch805: openssh-7.2p2-k5login_directory.patch -# Rewriting OpenSSH GSS KEX to use new packet API -Patch806: openssh-9.6p1-gsskex-new-api.patch #https://bugzilla.mindrot.org/show_bug.cgi?id=1780 Patch901: openssh-6.6p1-kuserok.patch @@ -158,9 +156,6 @@ Patch953: openssh-7.8p1-scp-ipv6.patch # Mention crypto-policies in manual pages (#1668325) # clarify rhbz#2068423 on the man page of ssh_config Patch962: openssh-8.0p1-crypto-policies.patch -# Use OpenSSL high-level API to produce and verify signatures (#1707485) -# TODO fix the comment above ^ -Patch963: openssh-9.3p1-merged-openssl-evp.patch # Use OpenSSL KDF (#1631761) Patch964: openssh-8.0p1-openssl-kdf.patch # sk-dummy.so built with -fvisibility=hidden does not work @@ -196,8 +191,7 @@ Patch1002: openssh-8.7p1-ssh-manpage.patch # https://github.com/openssh/openssh-portable/pull/323 Patch1006: openssh-8.7p1-negotiate-supported-algs.patch -Patch1012: openssh-9.0p1-evp-fips-dh.patch -Patch1013: openssh-9.0p1-evp-fips-ecdh.patch +Patch1012: openssh-9.0p1-evp-fips-kex.patch Patch1014: openssh-8.7p1-nohostsha1proof.patch Patch1015: openssh-9.6p1-pam-rhost.patch @@ -332,7 +326,6 @@ gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0} %patch -P 801 -p1 -b .force_krb %patch -P 804 -p1 -b .ccache_name %patch -P 805 -p1 -b .k5login -%patch -P 806 -p1 -b .gsskex-new-api # %patch -P 901 -p1 -b .kuserok %patch -P 906 -p1 -b .fromto-remote @@ -349,7 +342,6 @@ gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0} %patch -P 951 -p1 -b .pkcs11-uri %patch -P 953 -p1 -b .scp-ipv6 %patch -P 962 -p1 -b .crypto-policies -%patch -P 963 -p1 -b .openssl-evp %patch -P 964 -p1 -b .openssl-kdf %patch -P 965 -p1 -b .visibility %patch -P 966 -p1 -b .x11-ipv6 @@ -373,7 +365,6 @@ gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0} %patch -P 1006 -p1 -b .negotiate-supported-algs %patch -P 1012 -p1 -b .evp-fips-dh -%patch -P 1013 -p1 -b .evp-fips-ecdh %patch -P 1014 -p1 -b .nosha1hostproof %patch -P 1015 -p1 -b .pam-rhost @@ -474,7 +465,7 @@ popd %endif %check -%{SOURCE22} %{SOURCE23} # ./parallel_tests.sh parallel_tests.Makefile +OPENSSL_CONF=/dev/null %{SOURCE22} %{SOURCE23} # ./parallel_tests.sh parallel_tests.Makefile %install rm -rf $RPM_BUILD_ROOT @@ -653,6 +644,10 @@ test -f %{sysconfig_anaconda} && \ %attr(0755,root,root) %{_libdir}/sshtest/sk-dummy.so %changelog +* Thu Oct 10 2024 Dmitry Belyavskiy - 9.9p1-1 +- Update to OpenSSH 9.9p1 + Resolves: RHEL-60564 + * Mon Sep 16 2024 Dmitry Belyavskiy - 9.8p1-6 - rebuilt Related: RHEL-59024 diff --git a/sources b/sources index 1f1ded8..e88b6d9 100644 --- a/sources +++ b/sources @@ -1,3 +1,3 @@ -SHA512 (openssh-9.8p1.tar.gz) = 95dec2f18e58eb47994f3de4430253e0665e185564b65088ca5f4108870e05feddef8cda8d3c0a4b75f18b98cc2c024df0e27de53b48c1a16da8da483cb8292a -SHA512 (openssh-9.8p1.tar.gz.asc) = 4df1f1be2c6ab7f3aebaedd0a773b0e8c8929abb30cd3415873ad55d012cfa113f792e888e5e772dd468c394aeb7e35d62893a514dbc0ab1a03acd79918657f7 +SHA512 (openssh-9.9p1.tar.gz) = 3cc0ed97f3e29ecbd882eca79239f02eb5a1606fce4f3119ddc3c5e86128aa3ff12dc85000879fccc87b60e7d651cfe37376607ac66075fede2118deaa685d6d +SHA512 (openssh-9.9p1.tar.gz.asc) = 916e975c54eb68c0b2f0b0006522b241cbe54c4caa88d31537a6278490c93d9d732c2ab3a080ac084bf75cbdd5402901ec68583cbe7c7cde4a8e40e7a8b78c28 SHA512 (gpgkey-736060BA.gpg) = df44f3fdbcd1d596705348c7f5aed3f738c5f626a55955e0642f7c6c082995cf36a1b1891bb41b8715cb2aff34fef1c877e0eff0d3507dd00a055ba695757a21