Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support authentication indicators in GSSAPI #500

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use match_pattern_list ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrote to use match_pattern_list. This forced a change in the way how a rule is presented, though not too different:

GSSAPIIndicators [!]name1,[!]name2,...

Where a negation would mean a denial to the presence of the indicator.
A result is that a rule like

GSSAPIIndicators !hardened,pkinit,idp,passkey,otp

would only allow access with passwordless pre-authentication method in FreeIPA (either using smartcards, external IdP authentication, FIDO2 token or TOTP/HOTP token).

This simplifies a bit both configuration part and matching rule.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this as a fixup on top of the original commit to show the difference. Let me know if this is better one. I also would like to add it to Match handling so that one could have per-group or per-user indicator rules. Let me know if this would be useful.

}
/* 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
Loading