GSSAPI KEX - #856
Conversation
This patch has been rejected upstream: "None of the OpenSSH developers are in favour of adding this, and this situation has not changed for several years. This is not a slight on Simon's patch, which is of fine quality, but just that a) we don't trust GSSAPI implementations that much and b) we don't like adding new KEX since they are pre-auth attack surface. This one is particularly scary, since it requires hooks out to typically root-owned system resources." However, quite a lot of people rely on this in Debian, and it's better to have it merged into the main openssh package rather than having separate -krb5 packages (as we used to have). It seems to have a generally good security history. Author: Simon Wilkinson <simon@sxw.org.uk> Author: Colin Watson <cjwatson@debian.org> Author: Jakub Jelen <jjelen@redhat.com> Origin: other, openssh-gsskex/openssh-gsskex#23 Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1242 Last-Updated: 2026-03-27 Patch-Name: gssapi.patch
There was a problem hiding this comment.
Pull request overview
This PR adds RFC 4462 GSSAPI-based key exchange (GSS KEX) support to OpenSSH portable, including a “null” hostkey algorithm to allow server authentication via GSSAPI without requiring traditional SSH host keys, plus the required configuration/options, monitor/privsep plumbing, and Windows/SSPI shim updates.
Changes:
- Implement GSSAPI key exchange (client + server) and wire it into kex negotiation, proposals, and rekey credential updates.
- Introduce a
nullhostkey algorithm (KEY_NULL) and adjust hostkey handling to support hostkey-less operation when GSS KEX is enabled. - Add client/server config options + manpage documentation, and update build systems (Makefile + Win32 .vcxproj) to compile the new sources.
Reviewed changes
Copilot reviewed 51 out of 51 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| sshkey.h | Add KEY_NULL key type for the “null” hostkey algorithm. |
| sshkey.c | Register sshkey_null_impl; hide null from key algorithm listings. |
| sshd.c | Allow starting sshd without hostkeys when GSSAPIKeyExchange is enabled. |
| sshd-session.c | Don’t fatal when no hostkeys exist (needed for hostkey-less GSS KEX). |
| sshd-auth.c | Server-side proposal/kex wiring for GSS KEX and null hostkey mode. |
| sshd_config.5 | Document GSSAPIKeyExchange, GSSAPIStoreCredentialsOnRekey, GSSAPIKexAlgorithms. |
| sshd_config | Add commented example lines for new GSS-related sshd options. |
| sshconnect2.c | Client-side proposal/kex wiring for GSS KEX and null hostkey fallback. |
| ssh.c | Add ssh -Q kex-gss query support. |
| ssh.1 | Document kex-gss query and new GSS-related ssh options in the option index. |
| ssh-null.c | Implement “null” sshkey backend for the null hostkey algorithm. |
| ssh-gss.h | Add GSS KEX constants, defaults, and new GSS helper prototypes/state. |
| ssh_config.5 | Document client-side GSSAPIKeyExchange and related options. |
| ssh_config | Add commented example lines for new GSS-related ssh client options. |
| session.c | Run Kerberos/GSS credential cleanup under the authenticated user’s UID. |
| servconf.h | Add server options: gss_keyex, gss_store_rekey, gss_kex_algorithms. |
| servconf.c | Parse/dump new server options and set defaults (incl. default GSS KEX list). |
| readconf.h | Add client options: gss_keyex, trust-dns, renewal-rekey, identities, kex alg list. |
| readconf.c | Parse/dump new client options and set defaults (incl. default GSS KEX list). |
| monitor.h | Add monitor request/answer IDs for GSS signing and credential update. |
| monitor.c | Permit and implement GSS signing + credential update requests across privsep. |
| monitor_wrap.h | Extend monitor wrapper APIs for GSS userok/sign/updatecreds. |
| monitor_wrap.c | Implement wrapper calls for GSS sign/updatecreds; extend gss userok args. |
| Makefile.in | Build/link new objects: ssh-null.o, kexgssc.o, kexgsss.o. |
| kexgsss.c | Add server-side GSS KEX implementations (DH/ECDH/C25519 + GEX variants). |
| kexgssc.c | Add client-side GSS KEX implementations (DH/ECDH/C25519 + GEX variants). |
| kexgen.c | Export kex_gen_hash() for reuse by GSS KEX implementations. |
| kexdh.c | Allow DH group selection for GSS KEX DH-based variants. |
| kex.h | Add GSS KEX enum values, kex->gss* fields, and new GSS kex helper prototypes. |
| kex.c | Free kex->gss_host during kex teardown. |
| kex-names.c | Add GSS KEX algorithm list + validation helpers and a kex_gss_alg_list() API. |
| gss-serv.c | Add server-side GSS KEX mechanism advertising/checking and rekey cred plumbing. |
| gss-serv-krb5.c | Add/update Kerberos ccache storage and implement credential update on rekey. |
| gss-genr.c | Add client mechanism enumeration/encoding for GSS KEX, OID mapping, renew detection. |
| contrib/win32/win32compat/inc/gssapi.h | Extend SSPI shim API surface for new GSS calls/types. |
| contrib/win32/win32compat/gss-sspi.c | Implement SSPI shims for name compare and cred inquiry; improve lifetime handling. |
| contrib/win32/openssh/sshd.vcxproj.filters | Adjust Win32 project filters (source list changes). |
| contrib/win32/openssh/sshd.vcxproj | Update Win32 sshd project compilation units (remove gss-serv sources here). |
| contrib/win32/openssh/sshd-session.vcxproj.filters | Add kexgsss.c to sshd-session project filters. |
| contrib/win32/openssh/sshd-session.vcxproj | Compile kexgsss.c in sshd-session Win32 project. |
| contrib/win32/openssh/sshd-auth.vcxproj.filters | Add kexgsss.c to sshd-auth project filters. |
| contrib/win32/openssh/sshd-auth.vcxproj | Compile kexgsss.c in sshd-auth Win32 project. |
| contrib/win32/openssh/libssh.vcxproj.filters | Add kexgssc.c and ssh-null.c to libssh project filters. |
| contrib/win32/openssh/libssh.vcxproj | Compile kexgssc.c and ssh-null.c in libssh Win32 project. |
| configure.ac | Add Apple Security Session / CCAPI detection (for credential cache handling). |
| clientloop.c | Force rekey when credentials renew (if configured) outside of active rekeying. |
| auth2.c | Register gssapi-keyex auth method on the server. |
| auth2-methods.c | Add gssapi-keyex method configuration wiring. |
| auth2-gss.c | Implement server-side gssapi-keyex userauth method and adjust gss userok calls. |
| auth.c | Permit root login via gssapi-keyex when configured similarly to other strong methods. |
| README.md | Add a GSS KEX-focused header section ahead of the upstream README content. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0) | ||
| myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = "null"; | ||
|
|
||
| if (newstr) | ||
| myproposal[PROPOSAL_KEX_ALGS] = newstr; | ||
| else | ||
| fatal("No supported key exchange algorithms"); |
| 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); | ||
| } |
| 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 */ |
| struct sshbuf *empty = sshbuf_new(); | ||
| int r; | ||
|
|
||
| /* Initialise GSSAPI */ |
|
@microsoft-github-policy-service agree company="The Old Chapel Technology, Ltd" |
|
Tess Gauthier (@tgauth) FYI this is super useful when ssh'ing around using Kerberos for authentication because it keeps sending fresh tickets one might need in the server-side. |
The GSS Curve25519 client path checked sshbuf_ptr(server_blob)[sshbuf_len(server_blob)] when rejecting public keys with the high bit set. That indexes one byte past the end of the sshbuf payload. The byte after Q_S is the first byte of the following MIC string length, encoded in network byte order. With OpenSSH's packet size limit this byte is normally zero, so the bug usually made the intended high-bit rejection a no-op rather than causing random failures. A peer can send a high-bit-set Q_S, but it still has to produce the GSS MIC over the exchange hash for the exact values used. This does not appear to provide key compromise or authentication bypass; it is a correctness fix for the validation policy carried by the GSS KEX patch. Reject an empty server blob explicitly, then test the last payload byte at sshbuf_len(server_blob) - 1 before passing the blob to kex_c25519_dec().
072c2fd to
14b9efb
Compare
|
The remaining issues that copilot raised are relatively minor and are all in the upstream GSSAPI KEX patch rather than the Windows code that I added. The right approach is likely to submit fixes upstream and add them here when they have been accepted. |
|
Roland C. Dowdeswell (@elric1) this repo's upstream is https://github.com/openssh/openssh-portable and they don't have any plans to take this patch AFAIK. Any of the deviations in common code (anything not in the |
|
I understand. This patch is incorporated by both Debian and Red Hat, but they mange it themselves. It is a reasonably high value update which they feel is worth the effort. Applying this patch and maintaining it would provide valuable interoperability with Red Hat and Debian. |
|
I agree with the interop value, but the guards are still a hard requirement before we can merge. I can add them to the common-code changes, if you'd rather not - just let me know! |
|
Okay. It might be quicker for you to add them, because you know exactly where you need them? |
| if (SecFunctions->QuerySecurityContextToken(*context_handle, &sspi_auth_user) != SEC_E_OK) | ||
| goto done; | ||
| (*delegated_cred_handle)->isToken = 1; | ||
| (*delegated_cred_handle)->token = sspi_auth_user; |
There was a problem hiding this comment.
Security: this publishes the GSS context token as a login token before it has been bound to the authorized SSH account.
GSS KEX completes before the requested SSH username is known. This means principal A's token can remain in sspi_auth_user while the client later authenticates to account B using public key or another non-password method. The unchanged spawn path then prefers A's token when creating B's session.
The token returned here could remain owned by the GSS credential rather than aliasing the global session-token slot:
| if (SecFunctions->QuerySecurityContextToken(*context_handle, &sspi_auth_user) != SEC_E_OK) | |
| goto done; | |
| (*delegated_cred_handle)->isToken = 1; | |
| (*delegated_cred_handle)->token = sspi_auth_user; | |
| if (SecFunctions->QuerySecurityContextToken(*context_handle, | |
| &(*delegated_cred_handle)->token) != SEC_E_OK) | |
| goto done; |
The corresponding gss_release_cred() alias handling in contrib/win32/win32compat/gss-sspi.c would no longer be needed:
if ((*cred_handle)->token != NULL)
CloseHandle((*cred_handle)->token);Then ssh_gssapi_krb5_userok() in contrib/win32/win32compat/gss-sspi.c could verify that the credential token belongs to the resolved SSH account and create a separately owned primary session token:
if (!sspi_token_matches_user(client->creds->token, user->pw_name)) {
error("sspi token does not belong to resolved user '%s'", name);
return 0;
}
if (!DuplicateTokenEx(client->creds->token,
TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, NULL,
SecurityImpersonation, TokenPrimary, &session_token)) {
error("DuplicateTokenEx failed with %lu", GetLastError());
return 0;
}
if (sspi_auth_user != NULL)
CloseHandle(sspi_auth_user);
sspi_auth_user = session_token;The spawn path in contrib/win32/win32compat/spawn-ext.c would also need to transfer ownership before closing the token:
} else if (sspi_auth_user != NULL) {
user_token = sspi_auth_user;
sspi_auth_user = NULL;
}Could you use this credential-owned context token and separately owned session token model so that the session token is created only after SID validation and cannot remain aliased after spawn?
| (ctx->major = gss_inquire_cred_by_mech(&ctx->minor, | ||
| ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) { |
There was a problem hiding this comment.
Repeated GSS rekeys appear to overwrite owned client state without releasing it.
The narrow update branch is only taken when gss_store_rekey, client->used, and ctx->client_creds are all set. With the default configuration, or when the connection authenticates through a non-GSS method such as public key, a subsequent successful GSS rekey enters this ordinary path again.
This path writes directly into client->name, displayname, exportedname, and creds even though those fields may still own resources from the previous GSS exchange. Each rekey can therefore orphan the previous names and buffers. On Windows, the replaced SSPI credential also owns a token HANDLE.
A safer structure would be to populate temporary values without modifying the live client:
gss_name_t new_name = GSS_C_NO_NAME;
gss_buffer_desc new_displayname = GSS_C_EMPTY_BUFFER;
gss_buffer_desc new_exportedname = GSS_C_EMPTY_BUFFER;
/* Populate and validate the temporary identity state first. */Only after all operations succeed would the old identity state be released and the new state committed:
ssh_gssapi_release_client_identity(client, &ctx->minor);
client->name = new_name;
client->displayname = new_displayname;
client->exportedname = new_exportedname;
client->creds = ctx->client_creds;
ctx->client_creds = GSS_C_NO_CREDENTIAL;The error path would release the temporary values instead, leaving the existing client intact. The narrow rekey branch would also need to release new_name before returning after a comparison error or name mismatch.
Could this function use a temporary-and-swap pattern with centralized identity cleanup so that each GSS name, buffer, credential, and platform handle always has exactly one live owner?
PR Summary
Add GSSAPI KEX.
PR Context
In an environment which uses GSS, this patch will use GSS to authenticate the remote server which eliminates the need to use SSH host keys.
GSS KEX is defined by RFC 4462.
The base GSSAPI key exchange implementation comes from Simon Wilkinson's patch, carried and maintained by Debian for OpenSSH. We add the Windows build and SSPI shim adaptations needed.
Fixes 15..