Skip to content

Commit

Permalink
support authentication indicators in GSSAPI
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
abbra committed Sep 9, 2024
1 parent e8a0f19 commit 7a0bab8
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 5 deletions.
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -4882,6 +4882,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])])
Expand Down
60 changes: 57 additions & 3 deletions gss-serv-krb5.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "log.h"
#include "misc.h"
#include "servconf.h"
#include "match.h"

#include "ssh-gss.h"

Expand Down Expand Up @@ -76,6 +77,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
Expand All @@ -85,7 +112,7 @@ static int
ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
{
krb5_principal princ;
int retval;
int retval, matched;
const char *errmsg;

if (ssh_gssapi_krb5_init() == 0)
Expand All @@ -100,11 +127,38 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
}
if (krb5_kuserok(krb_context, princ, name)) {
retval = 1;
logit("Authorized to %s, krb5 principal %s (krb5_kuserok)",
name, (char *)client->displayname.value);
errmsg = "krb5_kuserok";
} 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;
}
Expand Down
104 changes: 103 additions & 1 deletion gss-serv.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ 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_NO_CREDENTIAL, NULL, {NULL, NULL, NULL, NULL}, NULL};

ssh_gssapi_mech gssapi_null_mech =
{ NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL};
Expand Down Expand Up @@ -268,6 +268,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 */

Expand Down Expand Up @@ -309,6 +395,13 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
return (ctx->major);
}

/* 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;
ctx->client_creds = GSS_C_NO_CREDENTIAL;
Expand Down Expand Up @@ -359,6 +452,7 @@ int
ssh_gssapi_userok(char *user)
{
OM_uint32 lmin;
size_t i;

if (gssapi_client.exportedname.length == 0 ||
gssapi_client.exportedname.value == NULL) {
Expand All @@ -373,6 +467,14 @@ ssh_gssapi_userok(char *user)
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;
Expand Down
15 changes: 14 additions & 1 deletion servconf.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ initialize_server_options(ServerOptions *options)
options->gss_authentication=-1;
options->gss_cleanup_creds = -1;
options->gss_strict_acceptor = -1;
options->gss_indicators = NULL;
options->password_authentication = -1;
options->kbd_interactive_authentication = -1;
options->permit_empty_passwd = -1;
Expand Down Expand Up @@ -557,7 +558,7 @@ typedef enum {
sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize,
sPerSourcePenalties, sPerSourcePenaltyExemptList,
sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor,
sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, sGssIndicators,
sAcceptEnv, sSetEnv, sPermitTunnel,
sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
sUsePrivilegeSeparation, sAllowAgentForwarding,
Expand Down Expand Up @@ -644,10 +645,12 @@ static struct {
{ "gssapiauthentication", sGssAuthentication, SSHCFG_ALL },
{ "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL },
{ "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL },
{ "gssapiindicators", sGssIndicators, SSHCFG_ALL },
#else
{ "gssapiauthentication", sUnsupported, SSHCFG_ALL },
{ "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
{ "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL },
{ "gssapiindicators", sUnsupported, SSHCFG_ALL },
#endif
{ "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL },
{ "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL },
Expand Down Expand Up @@ -1593,6 +1596,15 @@ process_server_config_line_depth(ServerOptions *options, char *line,
intptr = &options->gss_strict_acceptor;
goto parse_flag;

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;
Expand Down Expand Up @@ -3179,6 +3191,7 @@ dump_config(ServerOptions *o)
#ifdef GSSAPI
dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds);
dump_cfg_string(sGssIndicators, o->gss_indicators);
#endif
dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
dump_cfg_fmtint(sKbdInteractiveAuthentication,
Expand Down
2 changes: 2 additions & 0 deletions servconf.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ typedef struct {
char **allow_groups;
u_int num_deny_groups;
char **deny_groups;
char *gss_indicators;

u_int num_subsystems;
char **subsystem_name;
Expand Down Expand Up @@ -296,6 +297,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); \
Expand Down
7 changes: 7 additions & 0 deletions ssh-gss.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -74,6 +80,7 @@ typedef struct {
gss_cred_id_t creds;
struct ssh_gssapi_mech_struct *mech;
ssh_gssapi_ccache store;
char **indicators; /* auth indicators */
} ssh_gssapi_client;

typedef struct ssh_gssapi_mech_struct {
Expand Down
44 changes: 44 additions & 0 deletions sshd_config.5
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,50 @@ machine's default store.
This facility is provided to assist with operation on multi homed machines.
The default is
.Cm yes .
.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
Specifies the signature algorithms that will be accepted for hostbased
authentication as a list of comma-separated patterns.
Expand Down

0 comments on commit 7a0bab8

Please sign in to comment.