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 Jun 14, 2024
1 parent cfe243c commit 74cb7dd
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 4 deletions.
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -4862,6 +4862,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
74 changes: 72 additions & 2 deletions gss-serv-krb5.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,52 @@ 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.
* If there is no match:
* - if there were both denial and allow rules, deny
* - if there were only denial rules, allow
* - if there were only allow rules, deny
*/

static int
ssh_gssapi_check_indicators(ssh_gssapi_client *client, int *matched)
{
char *chptr;
int denial, allows = 0, denials = 0;
u_int i, j;

/* Check indicators */
for (i = 0; i < options.num_gss_indicators; i++) {
for (j = 0; client->indicators[j] != NULL; j++) {
chptr = strchr(options.gss_indicators[i], '=');
chptr[0] = '\0';
if (strcmp(client->indicators[j],
options.gss_indicators[i]) == 0) {
*matched = j;
}
chptr[0] = '=';
denial = (chptr[1] == 'd' || chptr[1] == 'D') ? 1 : 0;
denials |= denial;
allows |= !denial;
if (*matched) {
return (1 - denial);
}
}
}
/* No matches.
* If there were both types of rules, deny */
if (denials && allows)
return 0;
/* If there are only denials, allow the rest */
if (denials) {
return 1;
}
/* If there are only allows, deny the rest */
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 @@ -87,6 +133,7 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
krb5_principal princ;
int retval;
const char *errmsg;
int matched;

if (ssh_gssapi_krb5_init() == 0)
return 0;
Expand All @@ -100,11 +147,34 @@ 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.num_gss_indicators != 0)) {
matched = -1;
if (client->indicators) {
retval = ssh_gssapi_check_indicators(client, &matched);
if (matched != -1) {
logit("Ticket contains indicator %s, "
"krb5 principal %s is %s",
client->indicators[matched],
(char *)client->displayname.value,
retval ? "allowed" : "denied");
goto cont;
}
}
if ((matched == -1) && (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
103 changes: 102 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,91 @@ 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 (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 (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) {
(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);
}

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++;
}
}
}

(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 +394,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 +451,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 +466,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
2 changes: 2 additions & 0 deletions readconf.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ typedef struct {
int hostbased_authentication; /* ssh2's rhosts_rsa */
int gss_authentication; /* Try GSS authentication */
int gss_deleg_creds; /* Delegate GSS credentials */
u_int num_gss_indicators;
char **gss_indicators; /* GSSAPI authentication indicators */
int password_authentication; /* Try password
* authentication. */
int kbd_interactive_authentication; /* Try keyboard-interactive auth. */
Expand Down
43 changes: 42 additions & 1 deletion servconf.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ initialize_server_options(ServerOptions *options)
options->gss_authentication=-1;
options->gss_cleanup_creds = -1;
options->gss_strict_acceptor = -1;
options->gss_indicators = NULL;
options->num_gss_indicators = 0;
options->password_authentication = -1;
options->kbd_interactive_authentication = -1;
options->permit_empty_passwd = -1;
Expand Down Expand Up @@ -544,7 +546,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 @@ -629,10 +631,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 @@ -1568,6 +1572,41 @@ process_server_config_line_depth(ServerOptions *options, char *line,
intptr = &options->gss_strict_acceptor;
goto parse_flag;

case sGssIndicators:
found = options->num_gss_indicators == 0;
while ((arg = argv_next(&ac, &av)) != NULL) {
if (*arg == '\0') {
error("%s line %d: keyword %s empty argument",
filename, linenum, keyword);
goto out;
}
p = strchr(arg, '=');
if (p == NULL) {
error("%s line %d: Invalid %s, must be name=accept|deny",
filename, linenum, keyword);
goto out;
}
p++;
if (!(*p == 'a' || *p == 'A' || *p == 'd' || *p == 'D')) {
error("%s line %d: Invalid %s, must be name=accept|deny",
filename, linenum, keyword);
goto out;
}
opt_array_append(filename, linenum, keyword,
&strs, &nstrs, arg);
}
if (nstrs == 0) {
fatal("%s line %d: no %s specified",
filename, linenum, keyword);
}
if (found && *activep) {
options->gss_indicators = strs;
options->num_gss_indicators = nstrs;
strs = NULL; /* transferred */
nstrs = 0;
}
break;

case sPasswordAuthentication:
intptr = &options->password_authentication;
goto parse_flag;
Expand Down Expand Up @@ -3141,6 +3180,8 @@ dump_config(ServerOptions *o)
#ifdef GSSAPI
dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds);
dump_cfg_strarray_oneline(sGssIndicators, o->num_gss_indicators,
o->gss_indicators);
#endif
dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
dump_cfg_fmtint(sKbdInteractiveAuthentication,
Expand Down
3 changes: 3 additions & 0 deletions servconf.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ typedef struct {
char **allow_groups;
u_int num_deny_groups;
char **deny_groups;
u_int num_gss_indicators;
char **gss_indicators;

u_int num_subsystems;
char **subsystem_name;
Expand Down Expand Up @@ -297,6 +299,7 @@ TAILQ_HEAD(include_list, include_item);
M_CP_STRARRAYOPT(deny_users, num_deny_users); \
M_CP_STRARRAYOPT(allow_groups, num_allow_groups); \
M_CP_STRARRAYOPT(deny_groups, num_deny_groups); \
M_CP_STRARRAYOPT(gss_indicators, num_gss_indicators); \
M_CP_STRARRAYOPT(accept_env, num_accept_env); \
M_CP_STRARRAYOPT(setenv, num_setenv); \
M_CP_STRARRAYOPT(auth_methods, num_auth_methods); \
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
Loading

0 comments on commit 74cb7dd

Please sign in to comment.