From f897faab4293baa2b19966460a8a275b8af5ebfb Mon Sep 17 00:00:00 2001 From: Dmitry Belyavskiy Date: Thu, 26 Jun 2025 17:00:42 +0200 Subject: [PATCH] Support for authentication indicators in OpenSSH Resolves: RHEL-40790 --- ...-authentication-indicators-in-GSSAPI.patch | 450 ++++++++++++++++++ openssh.spec | 9 +- 2 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch diff --git a/openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch b/openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch new file mode 100644 index 0000000..237e45d --- /dev/null +++ b/openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch @@ -0,0 +1,450 @@ +From 5d5a66e96ad03132f65371070f4fa475f10207d9 Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Mon, 10 Jun 2024 23:00:03 +0300 +Subject: [PATCH] support authentication indicators in GSSAPI + +RFC 6680 defines a set of GSSAPI extensions to handle attributes +associated with the GSSAPI names. MIT Kerberos and FreeIPA use +name attributes to add information about pre-authentication methods used +to acquire the initial Kerberos ticket. The attribute 'auth-indicators' +may contain list of strings that KDC has associated with the ticket +issuance process. + +Use authentication indicators to authorise or deny access to SSH server. +GSSAPIIndicators setting allows to specify a list of possible indicators +that a Kerberos ticket presented must or must not contain. More details +on the syntax are provided in sshd_config(5) man page. + +Fixes: https://bugzilla.mindrot.org/show_bug.cgi?id=2696 + +Signed-off-by: Alexander Bokovoy +--- + configure.ac | 1 + + gss-serv-krb5.c | 64 +++++++++++++++++++++++++++--- + gss-serv.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++- + servconf.c | 15 ++++++- + servconf.h | 2 + + ssh-gss.h | 7 ++++ + sshd_config.5 | 44 +++++++++++++++++++++ + 7 files changed, 228 insertions(+), 8 deletions(-) + +diff --git a/configure.ac b/configure.ac +index d92a85809..2cbe20bf3 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -5004,6 +5004,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 --git a/gss-serv-krb5.c b/gss-serv-krb5.c +index 03188d9b3..2c786ef14 100644 +--- a/gss-serv-krb5.c ++++ b/gss-serv-krb5.c +@@ -43,6 +43,7 @@ + #include "log.h" + #include "misc.h" + #include "servconf.h" ++#include "match.h" + + #include "ssh-gss.h" + +@@ -87,6 +88,32 @@ 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; ++ ++ /* 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,7 +220,7 @@ static int + ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name) + { + krb5_principal princ; +- int retval; ++ int retval, matched; + const char *errmsg; + int k5login_exists; + +@@ -216,17 +243,42 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name) + 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); ++ errmsg = "ssh_gssapi_krb5_cmdok"; + } else + retval = 0; + ++ if ((retval == 1) && (options.gss_indicators != NULL)) { ++ /* At this point the configuration enforces presence of indicators ++ * so we drop the authorization result again */ ++ retval = 0; ++ if (client->indicators) { ++ matched = -1; ++ retval = ssh_gssapi_check_indicators(client, &matched); ++ if (retval != 0) { ++ retval = (retval == 1); ++ logit("Ticket contains indicator %s, " ++ "krb5 principal %s is %s", ++ client->indicators[matched], ++ (char *)client->displayname.value, ++ retval ? "allowed" : "denied"); ++ goto cont; ++ } ++ } ++ if (retval == 0) { ++ logit("GSSAPI authentication indicators enforced " ++ "but not matched. krb5 principal %s denied", ++ (char *)client->displayname.value); ++ } ++ } ++cont: ++ 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 --git a/gss-serv.c b/gss-serv.c +index 9d5435eda..5c0491cf1 100644 +--- a/gss-serv.c ++++ b/gss-serv.c +@@ -54,7 +54,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}; +@@ -296,6 +296,92 @@ ssh_gssapi_parse_ename(Gssctxt *ctx, gss_buffer_t ename, gss_buffer_t name) + 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 a utf8 string. */ ++ ++#define AUTH_INDICATORS_TAG "auth-indicators" ++ ++/* 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); ++ } ++ ++ count = 0; ++ for (i = 0; i < attrs->count; i++) { ++ /* skip anything but auth-indicators */ ++ if (((sizeof(AUTH_INDICATORS_TAG) - 1) != attrs->elements[i].length) || ++ strncmp(AUTH_INDICATORS_TAG, ++ attrs->elements[i].value, ++ sizeof(AUTH_INDICATORS_TAG) - 1) != 0) ++ continue; ++ count++; ++ } ++ ++ if (count == 0) { ++ /* No auth-indicators in the ticket */ ++ (void) gss_release_buffer_set(&ctx->minor, &attrs); ++ return (0); ++ } ++ ++ client->indicators = recallocarray(NULL, 0, count + 1, sizeof(char*)); ++ 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) || ++ strncmp(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) { ++ client->indicators[count] = xmalloc(value.length + 1); ++ memcpy(client->indicators[count], value.value, value.length); ++ client->indicators[count][value.length] = '\0'; ++ count++; ++ } ++ } ++ } ++ ++out: ++ (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 */ + +@@ -385,6 +471,12 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) + } + + 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; +@@ -447,6 +539,7 @@ int + ssh_gssapi_userok(char *user, struct passwd *pw, int kex) + { + OM_uint32 lmin; ++ size_t i; + + (void) kex; /* used in privilege separation */ + +@@ -465,6 +558,14 @@ ssh_gssapi_userok(char *user, struct passwd *pw, int kex) + gss_release_buffer(&lmin, &gssapi_client.displayname); + gss_release_buffer(&lmin, &gssapi_client.exportedname); + gss_release_cred(&lmin, &gssapi_client.creds); ++ ++ 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; +diff --git a/servconf.c b/servconf.c +index e7e4ad046..aab653244 100644 +--- a/servconf.c ++++ b/servconf.c +@@ -147,6 +147,7 @@ initialize_server_options(ServerOptions *options) + options->gss_strict_acceptor = -1; + options->gss_store_rekey = -1; + options->gss_kex_algorithms = NULL; ++ options->gss_indicators = NULL; + options->use_kuserok = -1; + options->enable_k5users = -1; + options->password_authentication = -1; +@@ -598,7 +599,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, +@@ -694,6 +695,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 }, +@@ -703,6 +705,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 }, +@@ -1730,6 +1733,15 @@ process_server_config_line_depth(ServerOptions *options, char *line, + 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; +@@ -3351,6 +3363,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 --git a/servconf.h b/servconf.h +index 7c7e5d434..7c41df417 100644 +--- a/servconf.h ++++ b/servconf.h +@@ -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; +@@ -310,6 +311,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 --git a/ssh-gss.h b/ssh-gss.h +index a894e23c9..59cf46d47 100644 +--- a/ssh-gss.h ++++ b/ssh-gss.h +@@ -34,6 +34,12 @@ + #include + #endif + ++#ifdef HAVE_GSSAPI_EXT_H ++#include ++#elif defined(HAVE_GSSAPI_GSSAPI_EXT_H) ++#include ++#endif ++ + #ifdef KRB5 + # ifndef HEIMDAL + # ifdef HAVE_GSSAPI_GENERIC_H +@@ -107,6 +113,7 @@ typedef struct { + ssh_gssapi_ccache store; + int used; + int updated; ++ char **indicators; /* auth indicators */ + } ssh_gssapi_client; + + typedef struct ssh_gssapi_mech_struct { +diff --git a/sshd_config.5 b/sshd_config.5 +index 583a01cdb..90ab87edd 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -785,6 +785,50 @@ 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 is to not use GSSAPI authentication indicators for access decisions. + .It Cm HostbasedAcceptedAlgorithms + The default is handled system-wide by + .Xr crypto-policies 7 . +-- +2.49.0 + diff --git a/openssh.spec b/openssh.spec index fe5fcdd..1280fb6 100644 --- a/openssh.spec +++ b/openssh.spec @@ -43,7 +43,7 @@ Summary: An open source implementation of SSH protocol version 2 Name: openssh Version: %{openssh_ver} -Release: 9%{?dist} +Release: 10%{?dist} URL: http://www.openssh.com/portable.html Source0: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz Source1: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz.asc @@ -212,6 +212,8 @@ Patch1023: openssh-9.9p1-compression-directive.patch Patch1024: openssh-9.9p1-disable-forwarding.patch Patch1025: openssh-9.9p1-non-supported-keys-err-msg.patch Patch1026: openssh-9.9p1-bad-hostkey.patch +# https://github.com/openssh/openssh-portable/pull/500 +Patch1027: openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch License: BSD-3-Clause AND BSD-2-Clause AND ISC AND SSH-OpenSSH AND ssh-keyscan AND sprintf AND LicenseRef-Fedora-Public-Domain AND X11-distribute-modifications-variant Requires: /sbin/nologin @@ -403,6 +405,7 @@ gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0} %patch -P 1024 -p1 -b .disable-forwarding %patch -P 1025 -p1 -b .non-supported-keys-err-msg %patch -P 1026 -p1 -b .bad-hostkey +%patch -P 1027 -p1 -b .gss-indicators %patch -P 100 -p1 -b .coverity @@ -683,6 +686,10 @@ test -f %{sysconfig_anaconda} && \ %attr(0755,root,root) %{_libdir}/sshtest/sk-dummy.so %changelog +* Thu Jun 26 2025 Dmitry Belyavskiy - 9.9p1-10 +- Support for authentication indicators in OpenSSH + Resolves: RHEL-40790 + * Tue Apr 29 2025 Zoltan Fridrich - 9.9p1-9 - CVE-2025-32728: Fix logic error in DisableForwarding option Resolves: RHEL-86819