gsskrb5: Add cred store PKINIT and FAST options

This commit is contained in:
Nicolas Williams
2026-01-05 23:42:06 -06:00
parent 509ee48669
commit b804b22446

View File

@@ -71,6 +71,58 @@ __gsskrb5_cred_store_find(OM_uint32 *minor_status,
return GSS_S_COMPLETE;
}
/*
* Find all elements matching a key in a cred store. Returns a NULL-terminated
* array of strings. Caller must free the array (but not the strings, which
* point into the cred_store). Returns NULL if no matches or on allocation
* failure.
*
* Note: returns char ** (not const char **) for compatibility with
* krb5_get_init_creds_opt_set_pkinit() which takes char * const *.
*/
static char **
cred_store_find_all(gss_const_key_value_set_t cred_store,
const char *key,
size_t *count_out)
{
char **values = NULL;
size_t count = 0;
size_t i;
if (count_out)
*count_out = 0;
if (cred_store == GSS_C_NO_CRED_STORE || cred_store->count == 0)
return NULL;
/* Count matches */
for (i = 0; i < cred_store->count; i++) {
if (strcmp(key, cred_store->elements[i].key) == 0)
count++;
}
if (count == 0)
return NULL;
/* Allocate array (count + 1 for NULL terminator) */
values = calloc(count + 1, sizeof(*values));
if (values == NULL)
return NULL;
/* Fill array - cast away const for API compatibility */
count = 0;
for (i = 0; i < cred_store->count; i++) {
if (strcmp(key, cred_store->elements[i].key) == 0)
values[count++] = rk_UNCONST(cred_store->elements[i].value);
}
values[count] = NULL;
if (count_out)
*count_out = count;
return values;
}
OM_uint32
__gsskrb5_ccache_lifetime(OM_uint32 *minor_status,
krb5_context context,
@@ -187,6 +239,185 @@ is_valid_password_cred_store(gss_const_key_value_set_t cred_store)
return TRUE;
}
/*
* Check if the cred store contains PKINIT-related keys or if an anonymous
* principal should trigger anonymous PKINIT.
*/
static krb5_boolean
is_pkinit_cred_store(krb5_context context,
krb5_const_principal principal,
gss_const_key_value_set_t cred_store,
const char **user_id,
const char **x509_anchors,
krb5_boolean *anonymous,
krb5_boolean *fast_anon_pkinit,
krb5_boolean *fast_optimistic)
{
size_t i;
*user_id = NULL;
*x509_anchors = NULL;
*anonymous = FALSE;
*fast_anon_pkinit = FALSE;
*fast_optimistic = FALSE;
/* Check if principal is anonymous - triggers anonymous PKINIT */
if (principal != NULL &&
krb5_principal_is_anonymous(context, principal, KRB5_ANON_MATCH_ANY))
*anonymous = TRUE;
if (cred_store != GSS_C_NO_CRED_STORE) {
for (i = 0; i < cred_store->count; i++) {
if (strcmp(cred_store->elements[i].key, "pkinit_client_certs") == 0)
*user_id = cred_store->elements[i].value;
else if (strcmp(cred_store->elements[i].key, "pkinit_trust_anchors") == 0)
*x509_anchors = cred_store->elements[i].value;
else if (strcmp(cred_store->elements[i].key, "fast_anon_pkinit") == 0)
*fast_anon_pkinit = TRUE;
else if (strcmp(cred_store->elements[i].key, "fast_anon_pkinit_optimistic") == 0)
*fast_optimistic = TRUE;
}
}
/* PKINIT if we have client certs or if anonymous */
return *user_id != NULL || *anonymous;
}
/*
* This function produces a cred with a MEMORY ccache containing a TGT
* acquired using PKINIT.
*/
static OM_uint32
acquire_cred_with_pkinit(OM_uint32 *minor_status,
krb5_context context,
const char *user_id,
const char *x509_anchors,
krb5_boolean anonymous,
krb5_boolean fast_anon_pkinit,
krb5_boolean fast_optimistic,
gss_const_key_value_set_t cred_store,
OM_uint32 time_req,
gss_OID_set desired_mechs,
gss_cred_usage_t cred_usage,
gsskrb5_cred handle)
{
OM_uint32 ret = GSS_S_FAILURE;
krb5_creds cred;
krb5_init_creds_context ctx = NULL;
krb5_get_init_creds_opt *opt = NULL;
krb5_ccache ccache = NULL;
krb5_error_code kret;
time_t now;
OM_uint32 left;
const char *realm;
char **pool = NULL;
char **pki_revoke = NULL;
int pkinit_flags = 0;
if (cred_usage == GSS_C_ACCEPT) {
*minor_status = ENOTSUP;
return GSS_S_FAILURE;
}
memset(&cred, 0, sizeof(cred));
/* Collect pool (intermediates) and revocation values */
pool = cred_store_find_all(cred_store, "pkinit_intermediates", NULL);
pki_revoke = cred_store_find_all(cred_store, "pkinit_revocation", NULL);
if (handle->principal == NULL) {
kret = krb5_get_default_principal(context, &handle->principal);
if (kret)
goto end;
}
realm = krb5_principal_get_realm(context, handle->principal);
kret = krb5_get_init_creds_opt_alloc(context, &opt);
if (kret)
goto end;
krb5_get_init_creds_opt_set_default_flags(context, "gss_krb5", realm, opt);
if (anonymous)
pkinit_flags |= KRB5_GIC_OPT_PKINIT_ANONYMOUS;
kret = krb5_get_init_creds_opt_set_pkinit(context, opt, handle->principal,
user_id, x509_anchors,
pool, pki_revoke,
pkinit_flags,
NULL, NULL, NULL);
if (kret)
goto end;
kret = krb5_init_creds_init(context, handle->principal, NULL, NULL, 0,
opt, &ctx);
if (kret)
goto end;
/* Set FAST anonymous PKINIT options after init_creds_init */
if (fast_optimistic) {
kret = _krb5_init_creds_set_fast_anon_pkinit_optimistic(context, ctx);
if (kret)
goto end;
} else if (fast_anon_pkinit) {
kret = krb5_init_creds_set_fast_anon_pkinit(context, ctx);
if (kret)
goto end;
}
krb5_timeofday(context, &now);
kret = krb5_init_creds_get(context, ctx);
if (kret)
goto end;
kret = krb5_init_creds_get_creds(context, ctx, &cred);
if (kret)
goto end;
kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
if (kret)
goto end;
kret = krb5_cc_initialize(context, ccache, cred.client);
if (kret)
goto end;
kret = krb5_init_creds_store(context, ctx, ccache);
if (kret)
goto end;
kret = krb5_cc_store_cred(context, ccache, &cred);
if (kret)
goto end;
handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache,
handle->principal, &left);
if (ret != GSS_S_COMPLETE)
goto end;
handle->endtime = now + left;
handle->ccache = ccache;
ccache = NULL;
ret = GSS_S_COMPLETE;
end:
free(pool);
free(pki_revoke);
krb5_get_init_creds_opt_free(context, opt);
if (ctx)
krb5_init_creds_free(context, ctx);
if (ccache != NULL)
krb5_cc_destroy(context, ccache);
if (cred.client != NULL)
krb5_free_cred_contents(context, &cred);
if (ret != GSS_S_COMPLETE)
*minor_status = kret;
return ret;
}
/*
* This function produces a cred with a MEMORY ccache containing a TGT
* acquired with a password.
@@ -577,6 +808,11 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_from
gsskrb5_cred handle;
OM_uint32 ret;
const char *password = NULL;
const char *pkinit_user_id = NULL;
const char *pkinit_anchors = NULL;
krb5_boolean pkinit_anonymous = FALSE;
krb5_boolean pkinit_fast_anon = FALSE;
krb5_boolean pkinit_fast_optimistic = FALSE;
if (desired_mechs) {
int present = 0;
@@ -627,7 +863,23 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_from
}
}
if (password) {
if (is_pkinit_cred_store(context, handle->principal, cred_store,
&pkinit_user_id, &pkinit_anchors,
&pkinit_anonymous, &pkinit_fast_anon,
&pkinit_fast_optimistic)) {
ret = acquire_cred_with_pkinit(minor_status, context,
pkinit_user_id, pkinit_anchors,
pkinit_anonymous, pkinit_fast_anon,
pkinit_fast_optimistic,
cred_store, time_req, desired_mechs,
cred_usage, handle);
if (ret != GSS_S_COMPLETE) {
HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
krb5_free_principal(context, handle->principal);
free(handle);
return ret;
}
} else if (password) {
ret = acquire_cred_with_password(minor_status, context, password, time_req,
desired_mechs, cred_usage, cred_store, handle);
if (ret != GSS_S_COMPLETE) {