kdc: support for GSS-API pre-authentication

Add support for GSS-API pre-authentication to the KDC, using a simplified
variation of draft-perez-krb-wg-gss-preauth-02 that encodes GSS-API context
tokens directly in PADATA, and uses FX-COOKIE for state management.

More information on the protocol and implementation may be found in
lib/gssapi/preauth/README.md.
This commit is contained in:
Luke Howard
2021-07-29 12:56:10 +10:00
parent 15c82996a4
commit 49f3f5bd99
40 changed files with 3132 additions and 90 deletions

View File

@@ -79,6 +79,8 @@ struct hx509_certs_data *ent_user_id = NULL;
char *pk_x509_anchors = NULL;
int pk_use_enckey = 0;
int pk_anon_fast_armor = 0;
char *gss_preauth_mech = NULL;
char *gss_preauth_name = NULL;
static int canonicalize_flag = 0;
static int enterprise_flag = 0;
static int ok_as_delegate_flag = 0;
@@ -188,6 +190,13 @@ static struct getargs args[] = {
{ "pk-anon-fast-armor", 0, arg_flag, &pk_anon_fast_armor,
NP_("use unauthenticated anonymous PKINIT as FAST armor", ""), NULL },
#endif
{ "gss-mech", 0, arg_string, &gss_preauth_mech,
NP_("use GSS mechanism for pre-authentication", ""), NULL },
{ "gss-name", 0, arg_string, &gss_preauth_name,
NP_("use distinct GSS identity for pre-authentication", ""), NULL },
#ifndef NO_NTLM
{ "ntlm-domain", 0, arg_string, &ntlm_domain,
NP_("NTLM domain", ""), "domain" },
@@ -570,6 +579,106 @@ out:
return ret;
}
static krb5_error_code
make_wellknown_name(krb5_context context,
krb5_const_realm realm,
const char *instance,
krb5_principal *principal)
{
krb5_error_code ret;
ret = krb5_make_principal(context, principal, realm,
KRB5_WELLKNOWN_NAME, instance, NULL);
if (ret == 0)
krb5_principal_set_type(context, *principal, KRB5_NT_WELLKNOWN);
return ret;
}
static krb5_error_code
acquire_gss_cred(krb5_context context,
krb5_const_principal client,
krb5_deltat life,
const char *passwd,
gss_cred_id_t *cred,
gss_OID *mech)
{
krb5_error_code ret;
OM_uint32 major, minor;
gss_name_t name = GSS_C_NO_NAME;
gss_key_value_element_desc cred_element;
gss_key_value_set_desc cred_store;
gss_OID_set_desc mechs;
*cred = GSS_C_NO_CREDENTIAL;
*mech = GSS_C_NO_OID;
if (gss_preauth_mech) {
*mech = gss_name_to_oid(gss_preauth_mech);
if (*mech == GSS_C_NO_OID)
return EINVAL;
}
if (gss_preauth_name) {
gss_buffer_desc buf;
buf.value = gss_preauth_name;
buf.length = strlen(gss_preauth_name);
major = gss_import_name(&minor, &buf, GSS_C_NT_USER_NAME, &name);
ret = _krb5_gss_map_error(major, minor);
} else if (!krb5_principal_is_federated(context, client)) {
ret = _krb5_gss_pa_unparse_name(context, client, &name);
} else {
/*
* WELLKNOWN/FEDERATED is used a placeholder where the user
* did not specify either a Kerberos credential or a GSS-API
* initiator name. It avoids the expense of acquiring a default
* credential purely to interrogate the credential name.
*/
name = GSS_C_NO_NAME;
ret = 0;
}
if (ret)
goto out;
cred_store.count = 1;
cred_store.elements = &cred_element;
if (passwd && passwd[0]) {
cred_element.key = "password";
cred_element.value = passwd;
} else if (keytab_str) {
cred_element.key = "client_keytab",
cred_element.value = keytab_str;
} else {
cred_store.count = 0;
}
if (*mech) {
mechs.count = 1;
mechs.elements = (gss_OID)*mech;
}
major = gss_acquire_cred_from(&minor,
name,
life ? life : GSS_C_INDEFINITE,
*mech ? &mechs : GSS_C_NO_OID_SET,
GSS_C_INITIATE,
&cred_store,
cred,
NULL,
NULL);
if (major != GSS_S_COMPLETE) {
ret = _krb5_gss_map_error(major, minor);
goto out;
}
out:
gss_release_name(&minor, &name);
return ret;
}
#ifndef NO_NTLM
static krb5_error_code
@@ -624,6 +733,9 @@ get_new_tickets(krb5_context context,
krb5_init_creds_context ctx = NULL;
krb5_get_init_creds_opt *opt = NULL;
krb5_prompter_fct prompter = krb5_prompter_posix;
gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
gss_OID gss_mech = GSS_C_NO_OID;
krb5_principal federated_name = NULL;
#ifndef NO_NTLM
struct ntlm_buf ntlmkey;
@@ -775,6 +887,29 @@ get_new_tickets(krb5_context context,
etype_str.num_strings);
}
if (gss_preauth_mech || gss_preauth_name) {
ret = acquire_gss_cred(context, principal, ticket_life,
passwd, &gss_cred, &gss_mech);
if (ret)
goto out;
/*
* The principal specified on the command line is used as the GSS-API
* initiator name, unless the --gss-name option was present, in which
* case the initiator name is specified independently.
*/
if (gss_preauth_name == NULL) {
krb5_const_realm realm = krb5_principal_get_realm(context, principal);
ret = make_wellknown_name(context, realm,
KRB5_FEDERATED_NAME, &federated_name);
if (ret)
goto out;
principal = federated_name;
}
}
ret = krb5_init_creds_init(context, principal, prompter, NULL, start_time, opt, &ctx);
if (ret) {
krb5_warn(context, ret, "krb5_init_creds_init");
@@ -816,7 +951,13 @@ get_new_tickets(krb5_context context,
}
}
if (use_keytab || keytab_str) {
if (gss_mech != GSS_C_NO_OID) {
ret = krb5_gss_set_init_creds(context, ctx, gss_cred, gss_mech);
if (ret) {
krb5_warn(context, ret, "krb5_gss_set_init_creds");
goto out;
}
} else if (use_keytab || keytab_str) {
ret = krb5_init_creds_set_keytab(context, ctx, kt);
if (ret) {
krb5_warn(context, ret, "krb5_init_creds_set_keytab");
@@ -979,6 +1120,11 @@ get_new_tickets(krb5_context context,
}
out:
{
OM_uint32 minor;
gss_release_cred(&minor, &gss_cred);
}
krb5_free_principal(context, federated_name);
krb5_get_init_creds_opt_free(context, opt);
if (ctx)
krb5_init_creds_free(context, ctx);
@@ -1489,17 +1635,20 @@ main(int argc, char **argv)
krb5_err(context, 1, ret, "krb5_pk_enterprise_certs");
pk_user_id = NULL;
} else if (argc && argv[0][0] == '@' &&
(gss_preauth_mech || anonymous_flag)) {
const char *instance;
} else if (anonymous_flag && argc && argv[0][0] == '@') {
/* If principal argument as @REALM, try anonymous PKINIT */
if (gss_preauth_mech) {
instance = KRB5_FEDERATED_NAME;
} else if (anonymous_flag) {
instance = KRB5_ANON_NAME;
anonymous_pkinit = TRUE;
}
ret = krb5_make_principal(context, &principal, &argv[0][1],
KRB5_WELLKNOWN_NAME, KRB5_ANON_NAME,
NULL);
ret = make_wellknown_name(context, &argv[0][1], instance, &principal);
if (ret)
krb5_err(context, 1, ret, "krb5_make_principal");
krb5_principal_set_type(context, principal, KRB5_NT_WELLKNOWN);
anonymous_pkinit = TRUE;
krb5_err(context, 1, ret, "make_wellknown_name");
} else if (anonymous_flag && historical_anon_pkinit) {
char *realm = argc == 0 ? get_default_realm(context) :
argv[0][0] == '@' ? &argv[0][1] : argv[0];
@@ -1512,6 +1661,15 @@ main(int argc, char **argv)
anonymous_pkinit = TRUE;
} else if (use_keytab || keytab_str) {
get_princ_kt(context, &principal, argv[0]);
} else if (gss_preauth_mech && argc == 0 && gss_preauth_name == NULL) {
/*
* Use the federated name as a placeholder if we have neither a Kerberos
* nor a GSS-API client name, and we are performing GSS-API preauth.
*/
ret = make_wellknown_name(context, get_default_realm(context),
KRB5_FEDERATED_NAME, &principal);
if (ret)
krb5_err(context, 1, ret, "make_wellknown_name");
} else {
get_princ(context, &principal, cred_cache, argv[0]);
}