openssh/openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch
Zoltan Fridrich 5112fd1dbb Fix GSSAPI authentication indicator issues found by AI
Resolves: RHEL-154309

Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
2026-03-12 16:44:06 +01:00

456 lines
16 KiB
Diff

diff --color -ruNp a/configure.ac b/configure.ac
--- a/configure.ac 2026-03-10 12:43:36.860784813 +0100
+++ b/configure.ac 2026-03-10 12:46:27.022297835 +0100
@@ -4932,6 +4932,7 @@ AC_ARG_WITH([kerberos5],
AC_CHECK_HEADERS([gssapi.h gssapi/gssapi.h])
AC_CHECK_HEADERS([gssapi_krb5.h gssapi/gssapi_krb5.h])
AC_CHECK_HEADERS([gssapi_generic.h gssapi/gssapi_generic.h])
+ AC_CHECK_HEADERS([gssapi_ext.h gssapi/gssapi_ext.h])
AC_SEARCH_LIBS([k_hasafs], [kafs], [AC_DEFINE([USE_AFS], [1],
[Define this if you want to use libkafs' AFS support])])
diff --color -ruNp a/gss-serv.c b/gss-serv.c
--- a/gss-serv.c 2026-03-10 12:43:36.802443034 +0100
+++ b/gss-serv.c 2026-03-12 10:04:37.520993330 +0100
@@ -53,7 +53,7 @@ extern ServerOptions options;
static ssh_gssapi_client gssapi_client =
{ GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
- GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0};
+ GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL};
ssh_gssapi_mech gssapi_null_mech =
{ NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
@@ -295,6 +295,95 @@ ssh_gssapi_parse_ename(Gssctxt *ctx, gss
return GSS_S_COMPLETE;
}
+
+/* Extract authentication indicators from the Kerberos ticket. Authentication
+ * indicators are GSSAPI name attributes for the name "auth-indicators".
+ * Multiple indicators might be present in the ticket.
+ * Each indicator is an utf8 string. */
+
+#define AUTH_INDICATORS_TAG "auth-indicators"
+#define SSH_GSSAPI_MAX_INDICATORS 64
+
+/* Privileged (called from accept_secure_ctx) */
+static OM_uint32
+ssh_gssapi_getindicators(Gssctxt *ctx, gss_name_t gss_name, ssh_gssapi_client *client)
+{
+ gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET;
+ gss_buffer_desc value = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc display_value = GSS_C_EMPTY_BUFFER;
+ int is_mechname, authenticated, complete, more;
+ size_t count, i;
+
+ ctx->major = gss_inquire_name(&ctx->minor, gss_name,
+ &is_mechname, NULL, &attrs);
+ if (ctx->major != GSS_S_COMPLETE) {
+ return (ctx->major);
+ }
+
+ if (attrs == GSS_C_NO_BUFFER_SET) {
+ /* No indicators in the ticket */
+ return (0);
+ }
+
+ client->indicators = NULL;
+ count = 0;
+ for (i = 0; i < attrs->count; i++) {
+ authenticated = 0;
+ complete = 0;
+ more = -1;
+ /* skip anything but auth-indicators */
+ if (((sizeof(AUTH_INDICATORS_TAG) - 1) != attrs->elements[i].length) ||
+ memcmp(AUTH_INDICATORS_TAG,
+ attrs->elements[i].value,
+ sizeof(AUTH_INDICATORS_TAG) - 1) != 0)
+ continue;
+ /* retrieve all indicators */
+ while (more != 0) {
+ value.value = NULL;
+ display_value.value = NULL;
+ ctx->major = gss_get_name_attribute(&ctx->minor, gss_name,
+ &attrs->elements[i], &authenticated,
+ &complete, &value, &display_value, &more);
+ if (ctx->major != GSS_S_COMPLETE)
+ goto out;
+
+ if ((value.value != NULL) && authenticated) {
+ if (count >= SSH_GSSAPI_MAX_INDICATORS) {
+ logit("ssh_gssapi_getindicators: too many "
+ "indicators, truncating at %d",
+ SSH_GSSAPI_MAX_INDICATORS);
+ /* value/display_value released at out: */
+ goto done;
+ }
+
+ client->indicators = xrecallocarray(client->indicators, count, count + 1, sizeof(char*));
+ if (client->indicators == NULL) {
+ fatal("ssh_gssapi_getindicators failed to allocate memory");
+ }
+ client->indicators[count] = xmalloc(value.length + 1);
+ memcpy(client->indicators[count], value.value, value.length);
+ client->indicators[count][value.length] = '\0';
+ count++;
+ }
+ }
+ }
+
+done:
+ /* slot [count] is zeroed by recallocarray, serves as NULL sentinel */
+
+out:
+ if (ctx->major != GSS_S_COMPLETE && client->indicators != NULL) {
+ for (i = 0; i < count; i++)
+ free(client->indicators[i]);
+ free(client->indicators);
+ client->indicators = NULL;
+ }
+ (void) gss_release_buffer(&ctx->minor, &value);
+ (void) gss_release_buffer(&ctx->minor, &display_value);
+ (void) gss_release_buffer_set(&ctx->minor, &attrs);
+ return (ctx->major);
+}
+
/* Extract the client details from a given context. This can only reliably
* be called once for a context */
@@ -384,6 +473,12 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_g
}
gss_release_buffer(&ctx->minor, &ename);
+ /* Retrieve authentication indicators, if they exist */
+ if ((ctx->major = ssh_gssapi_getindicators(ctx,
+ ctx->client, client))) {
+ ssh_gssapi_error(ctx);
+ return (ctx->major);
+ }
/* We can't copy this structure, so we just move the pointer to it */
client->creds = ctx->client_creds;
@@ -446,6 +541,7 @@ int
ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
{
OM_uint32 lmin;
+ size_t i;
(void) kex; /* used in privilege separation */
@@ -464,8 +560,14 @@ ssh_gssapi_userok(char *user, struct pas
gss_release_buffer(&lmin, &gssapi_client.displayname);
gss_release_buffer(&lmin, &gssapi_client.exportedname);
gss_release_cred(&lmin, &gssapi_client.creds);
- explicit_bzero(&gssapi_client,
- sizeof(ssh_gssapi_client));
+
+ if (gssapi_client.indicators != NULL) {
+ for (i = 0; gssapi_client.indicators[i] != NULL; i++)
+ free(gssapi_client.indicators[i]);
+ free(gssapi_client.indicators);
+ }
+
+ explicit_bzero(&gssapi_client, sizeof(ssh_gssapi_client));
return 0;
}
else
diff --color -ruNp a/gss-serv-krb5.c b/gss-serv-krb5.c
--- a/gss-serv-krb5.c 2026-03-10 12:43:36.823015336 +0100
+++ b/gss-serv-krb5.c 2026-03-11 12:58:56.024455238 +0100
@@ -43,6 +43,7 @@
#include "log.h"
#include "misc.h"
#include "servconf.h"
+#include "match.h"
#include "ssh-gss.h"
@@ -87,6 +88,33 @@ ssh_gssapi_krb5_init(void)
return 1;
}
+/* Check if any of the indicators in the Kerberos ticket match
+ * one of indicators in the list of allowed/denied rules.
+ * In case of the match, apply the decision from the rule.
+ * In case of no indicator from the ticket matching the rule, deny
+ */
+
+static int
+ssh_gssapi_check_indicators(ssh_gssapi_client *client, int *matched)
+{
+ int ret;
+ u_int i;
+ *matched = -1;
+
+ /* Check indicators */
+ for (i = 0; client->indicators[i] != NULL; i++) {
+ ret = match_pattern_list(client->indicators[i],
+ options.gss_indicators, 1);
+ /* negative or positive match */
+ if (ret != 0) {
+ *matched = i;
+ return ret;
+ }
+ }
+ /* No rule matched */
+ return 0;
+}
+
/* Check if this user is OK to login. This only works with krb5 - other
* GSSAPI mechanisms will need their own.
* Returns true if the user is OK to log in, otherwise returns 0
@@ -193,15 +221,15 @@ static int
ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
{
krb5_principal princ;
- int retval;
+ int retval, matched, success;
const char *errmsg;
int k5login_exists;
if (ssh_gssapi_krb5_init() == 0)
return 0;
- if ((retval = krb5_parse_name(krb_context, client->exportedname.value,
- &princ))) {
+ retval = krb5_parse_name(krb_context, client->exportedname.value, &princ);
+ if (retval) {
errmsg = krb5_get_error_message(krb_context, retval);
logit("krb5_parse_name(): %.100s", errmsg);
krb5_free_error_message(krb_context, errmsg);
@@ -216,17 +244,60 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client
if (k5login_exists &&
ssh_krb5_kuserok(krb_context, princ, name, k5login_exists)) {
retval = 1;
- logit("Authorized to %s, krb5 principal %s (krb5_kuserok)",
- name, (char *)client->displayname.value);
+ errmsg = "krb5_kuserok";
} else if (ssh_gssapi_krb5_cmdok(princ, client->exportedname.value,
name, k5login_exists)) {
retval = 1;
- logit("Authorized to %s, krb5 principal %s "
- "(ssh_gssapi_krb5_cmdok)",
- name, (char *)client->displayname.value);
- } else
+ errmsg = "ssh_gssapi_krb5_cmdok";
+ } else {
+ retval = 0;
+ goto out;
+ }
+
+ /* At this point we are good if no indicators were defined */
+ if (options.gss_indicators == NULL) {
+ retval = 1;
+ goto out;
+ }
+
+ /* At this point we have indicators defined in the configuration,
+ * if clientt did not provide any indicators, we reject */
+ if (!client->indicators) {
+ retval = 0;
+ logit("GSSAPI authentication indicators enforced "
+ "but indicators not provided by the client. "
+ "krb5 principal %s denied",
+ (char *)client->displayname.value);
+ goto out;
+ }
+
+ /* At this point the configuration enforces presence of indicators
+ * check the match */
+ matched = -1;
+ success = ssh_gssapi_check_indicators(client, &matched);
+
+ switch (success) {
+ case 1:
+ logit("Provided indicator %s allowed by the configuration",
+ client->indicators[matched]);
+ retval = 1;
+ break;
+ case -1:
+ logit("Provided indicator %s rejected by the configuration",
+ client->indicators[matched]);
+ retval = 0;
+ break;
+ default:
+ logit("Provided indicators do not match the configuration");
retval = 0;
+ break;
+ }
+out:
+ if (retval == 1) {
+ logit("Authorized to %s, krb5 principal %s (%s)",
+ name, (char *)client->displayname.value, errmsg);
+ }
krb5_free_principal(krb_context, princ);
return retval;
}
diff --color -ruNp a/servconf.c b/servconf.c
--- a/servconf.c 2026-03-10 12:43:36.928060353 +0100
+++ b/servconf.c 2026-03-11 13:20:09.725354925 +0100
@@ -144,6 +144,7 @@ initialize_server_options(ServerOptions
options->gss_keyex = -1;
options->gss_cleanup_creds = -1;
options->gss_strict_acceptor = -1;
+ options->gss_indicators = NULL;
options->gss_store_rekey = -1;
options->gss_kex_algorithms = NULL;
options->use_kuserok = -1;
@@ -557,6 +558,7 @@ fill_default_server_options(ServerOption
CLEAR_ON_NONE(options->routing_domain);
CLEAR_ON_NONE(options->host_key_agent);
CLEAR_ON_NONE(options->per_source_penalty_exempt);
+ CLEAR_ON_NONE(options->gss_indicators);
for (i = 0; i < options->num_host_key_files; i++)
CLEAR_ON_NONE(options->host_key_files[i]);
@@ -594,7 +596,7 @@ typedef enum {
sPerSourcePenalties, sPerSourcePenaltyExemptList,
sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
sGssAuthentication, sGssCleanupCreds, sGssEnablek5users, sGssStrictAcceptor,
- sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
+ sGssKeyEx, sGssIndicators, sGssKexAlgorithms, sGssStoreRekey,
sAcceptEnv, sSetEnv, sPermitTunnel,
sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
sUsePrivilegeSeparation, sAllowAgentForwarding,
@@ -690,6 +692,7 @@ static struct {
{ "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL },
{ "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
{ "gssapienablek5users", sGssEnablek5users, SSHCFG_ALL },
+ { "gssapiindicators", sGssIndicators, SSHCFG_ALL },
#else
{ "gssapiauthentication", sUnsupported, SSHCFG_ALL },
{ "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
@@ -699,6 +702,7 @@ static struct {
{ "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL },
{ "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
{ "gssapienablek5users", sUnsupported, SSHCFG_ALL },
+ { "gssapiindicators", sUnsupported, SSHCFG_ALL },
#endif
{ "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
{ "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
@@ -1715,6 +1719,15 @@ process_server_config_line_depth(ServerO
options->gss_kex_algorithms = xstrdup(arg);
break;
+ case sGssIndicators:
+ arg = argv_next(&ac, &av);
+ if (!arg || *arg == '\0')
+ fatal("%s line %d: %s missing argument.",
+ filename, linenum, keyword);
+ if (options->gss_indicators == NULL)
+ options->gss_indicators = xstrdup(arg);
+ break;
+
case sPasswordAuthentication:
intptr = &options->password_authentication;
goto parse_flag;
@@ -3329,6 +3342,7 @@ dump_config(ServerOptions *o)
dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor);
dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey);
dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms);
+ dump_cfg_string(sGssIndicators, o->gss_indicators);
#endif
dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
dump_cfg_fmtint(sKbdInteractiveAuthentication,
diff --color -ruNp a/servconf.h b/servconf.h
--- a/servconf.h 2026-03-10 12:43:36.833119920 +0100
+++ b/servconf.h 2026-03-11 13:21:36.742117033 +0100
@@ -181,6 +181,7 @@ typedef struct {
char **allow_groups;
u_int num_deny_groups;
char **deny_groups;
+ char *gss_indicators;
u_int num_subsystems;
char **subsystem_name;
@@ -309,6 +310,7 @@ TAILQ_HEAD(include_list, include_item);
M_CP_STROPT(routing_domain); \
M_CP_STROPT(permit_user_env_allowlist); \
M_CP_STROPT(pam_service_name); \
+ M_CP_STROPT(gss_indicators); \
M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files); \
M_CP_STRARRAYOPT(allow_users, num_allow_users); \
M_CP_STRARRAYOPT(deny_users, num_deny_users); \
diff --color -ruNp a/sshd_config.5 b/sshd_config.5
--- a/sshd_config.5 2026-03-10 12:43:36.859313302 +0100
+++ b/sshd_config.5 2026-03-11 13:28:04.541970063 +0100
@@ -785,6 +785,52 @@ gss-nistp256-sha256-
gss-curve25519-sha256-
.Ed
This option only applies to connections using GSSAPI.
+.It Cm GSSAPIIndicators
+Specifies whether to accept or deny GSSAPI authenticated access if Kerberos
+mechanism is used and Kerberos ticket contains a particular set of
+authentication indicators. The values can be specified as a comma-separated list
+.Cm [!]name1,[!]name2,... .
+When indicator's name is prefixed with !, the authentication indicator 'name'
+will deny access to the system. Otherwise, one of non-negated authentication
+indicators must be present in the Kerberos ticket to allow access. If
+.Cm GSSAPIIndicators
+is defined, a Kerberos ticket that has indicators but does not match the
+policy will get denial. If at least one indicator is configured, whether for
+access or denial, tickets without authentication indicators will be explicitly
+rejected.
+.Pp
+By default systems using MIT Kerberos 1.17 or later will not assign any
+indicators. SPAKE and PKINIT methods add authentication indicators
+to all successful authentications. The SPAKE pre-authentication method is
+preferred over an encrypted timestamp pre-authentication when passwords used to
+authenticate user principals. Kerberos KDCs built with Heimdal Kerberos
+(including Samba AD DC built with Heimdal) do not add authentication
+indicators. However, OpenSSH built against Heimdal Kerberos library is able to
+inquire authentication indicators and thus can be used to check for their presence.
+.Pp
+Indicator name is case-sensitive and depends on the configuration of a
+particular Kerberos deployment. Indicators available in MIT Kerberos and
+FreeIPA environments:
+.Pp
+.Bl -tag -width XXXX -offset indent -compact
+.It Cm hardened
+SPAKE or encrypted timestamp pre-authentication mechanisms in MIT Kerberos and FreeIPA
+.It Cm pkinit
+smartcard or PKCS11 token-based pre-authentication in MIT Kerberos and FreeIPA
+.It Cm radius
+pre-authentication based on a RADIUS server in MIT Kerberos and FreeIPA
+.It Cm otp
+TOTP/HOTP-based two-factor pre-authentication in FreeIPA
+.It Cm idp
+OAuth2-based pre-authentication in FreeIPA using an external identity provider
+and device authorization grant flow
+.It Cm passkey
+FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens
+.El
+.Pp
+The default
+.Dq none
+is to not use GSSAPI authentication indicators for access decisions.
.It Cm HostbasedAcceptedAlgorithms
The default is handled system-wide by
.Xr crypto-policies 7 .
diff --color -ruNp a/ssh-gss.h b/ssh-gss.h
--- a/ssh-gss.h 2026-03-10 12:43:36.898148309 +0100
+++ b/ssh-gss.h 2026-03-11 13:23:07.601956965 +0100
@@ -34,6 +34,12 @@
#include <gssapi/gssapi.h>
#endif
+#ifdef HAVE_GSSAPI_EXT_H
+#include <gssapi_ext.h>
+#elif defined(HAVE_GSSAPI_GSSAPI_EXT_H)
+#include <gssapi/gssapi_ext.h>
+#endif
+
#ifdef KRB5
# ifndef HEIMDAL
# ifdef HAVE_GSSAPI_GENERIC_H
@@ -112,6 +118,7 @@ typedef struct {
ssh_gssapi_ccache store;
int used;
int updated;
+ char **indicators; /* auth indicators */
} ssh_gssapi_client;
typedef struct ssh_gssapi_mech_struct {