openssh/openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch

451 lines
15 KiB
Diff

From 5d5a66e96ad03132f65371070f4fa475f10207d9 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
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 <abokovoy@redhat.com>
---
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 <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
@@ -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