Files
heimdal/kdc/gss_preauth.c
Luke Howard b6be850e0d kdc: remove outpadata from astgs_request_t
Remove the outpadata field from astgs_request_t, because it's not something we
wish to expose publically (yet it is something that Samba needs in the
client_access plugin API, to add Windows error information).

Instead, allocate rep->padata at the start of AS/TGS request handling, and
ensure it is valid for the lifetime of the request until it is encoded (at
which point it will be freed and set to NULL if zero length, to avoid sending a
zero length METHOD-DATA to the client).

(The previous approach of setting rep->padata to point to &r->outpadata was
fragile, because it required clearing the pointer before freeing the KDC-REP.)
2021-12-23 17:49:36 -06:00

1055 lines
32 KiB
C

/*
* Copyright (c) 2021, PADL Software Pty Ltd.
* All rights reserved.
*
* Portions Copyright (c) 2019 Kungliga Tekniska Högskolan
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of PADL Software nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "kdc_locl.h"
#include <gssapi/gssapi.h>
#include <gssapi_mech.h>
#include <gss-preauth-protos.h>
#include <gss-preauth-private.h>
#include "gss_preauth_authorizer_plugin.h"
struct gss_client_params {
OM_uint32 major, minor;
gss_ctx_id_t context_handle;
gss_name_t initiator_name;
gss_OID mech_type;
gss_buffer_desc output_token;
OM_uint32 flags;
OM_uint32 lifetime;
krb5_checksum req_body_checksum;
krb5_data pac_data;
};
static void
pa_gss_display_status(astgs_request_t r,
OM_uint32 major,
OM_uint32 minor,
gss_client_params *gcp,
const char *msg);
static void
pa_gss_display_name(gss_name_t name,
gss_buffer_t namebuf,
gss_const_buffer_t *namebuf_p);
/*
* Create a checksum over KDC-REQ-BODY (without the nonce), used to
* assert the request is invariant within the preauth conversation.
*/
static krb5_error_code
pa_gss_create_req_body_checksum(astgs_request_t r,
krb5_checksum *checksum)
{
krb5_error_code ret;
KDC_REQ_BODY b = r->req.req_body;
krb5_data data;
size_t size;
b.nonce = 0;
ASN1_MALLOC_ENCODE(KDC_REQ_BODY, data.data, data.length, &b, &size, ret);
heim_assert(ret || data.length,
"internal asn1 encoder error");
ret = krb5_create_checksum(r->context, NULL, 0, CKSUMTYPE_SHA256,
data.data, data.length, checksum);
krb5_data_free(&data);
return ret;
}
/*
* Verify a checksum over KDC-REQ-BODY (without the nonce), used to
* assert the request is invariant within the preauth conversation.
*/
static krb5_error_code
pa_gss_verify_req_body_checksum(astgs_request_t r,
krb5_checksum *checksum)
{
krb5_error_code ret;
KDC_REQ_BODY b = r->req.req_body;
krb5_data data;
size_t size;
b.nonce = 0;
ASN1_MALLOC_ENCODE(KDC_REQ_BODY, data.data, data.length, &b, &size, ret);
heim_assert(ret || data.length,
"internal asn1 encoder error");
ret = _kdc_verify_checksum(r->context, NULL, 0, &data, checksum);
krb5_data_free(&data);
return ret;
}
/*
* Decode the FX-COOKIE context state, consisting of the exported
* GSS context token concatenated with the checksum of the initial
* KDC-REQ-BODY.
*/
static krb5_error_code
pa_gss_decode_context_state(astgs_request_t r,
const krb5_data *state,
gss_buffer_t sec_context_token,
krb5_checksum *req_body_checksum)
{
krb5_error_code ret;
krb5_storage *sp;
size_t cksumsize;
krb5_data data;
memset(req_body_checksum, 0, sizeof(*req_body_checksum));
sec_context_token->length = 0;
sec_context_token->value = NULL;
krb5_data_zero(&data);
sp = krb5_storage_from_readonly_mem(state->data, state->length);
if (sp == NULL) {
ret = krb5_enomem(r->context);
goto out;
}
krb5_storage_set_eof_code(sp, KRB5_BAD_MSIZE);
krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_PACKED);
ret = krb5_ret_data(sp, &data);
if (ret)
goto out;
ret = krb5_ret_int32(sp, &req_body_checksum->cksumtype);
if (ret)
goto out;
if (req_body_checksum->cksumtype == CKSUMTYPE_NONE ||
krb5_checksum_is_keyed(r->context, req_body_checksum->cksumtype)) {
ret = KRB5KDC_ERR_SUMTYPE_NOSUPP;
goto out;
}
ret = krb5_checksumsize(r->context, req_body_checksum->cksumtype,
&cksumsize);
if (ret)
goto out;
req_body_checksum->checksum.data = malloc(cksumsize);
if (req_body_checksum->checksum.data == NULL) {
ret = krb5_enomem(r->context);
goto out;
}
if (krb5_storage_read(sp, req_body_checksum->checksum.data,
cksumsize) != cksumsize) {
ret = KRB5_BAD_MSIZE;
goto out;
}
req_body_checksum->checksum.length = cksumsize;
_krb5_gss_data_to_buffer(&data, sec_context_token);
out:
if (ret) {
krb5_data_free(&data);
free_Checksum(req_body_checksum);
memset(req_body_checksum, 0, sizeof(*req_body_checksum));
}
krb5_storage_free(sp);
return ret;
}
/*
* Deserialize a GSS-API security context from the FAST cookie.
*/
static krb5_error_code
pa_gss_get_context_state(astgs_request_t r,
gss_client_params *gcp)
{
int idx = 0;
PA_DATA *fast_pa;
krb5_error_code ret;
OM_uint32 major, minor;
gss_buffer_desc sec_context_token;
fast_pa = krb5_find_padata(r->fast.fast_state.val,
r->fast.fast_state.len,
KRB5_PADATA_GSS, &idx);
if (fast_pa == NULL)
return 0;
ret = pa_gss_decode_context_state(r, &fast_pa->padata_value,
&sec_context_token,
&gcp->req_body_checksum);
if (ret)
return ret;
ret = pa_gss_verify_req_body_checksum(r, &gcp->req_body_checksum);
if (ret) {
gss_release_buffer(&minor, &sec_context_token);
return ret;
}
major = gss_import_sec_context(&minor, &sec_context_token,
&gcp->context_handle);
if (GSS_ERROR(major)) {
pa_gss_display_status(r, major, minor, gcp,
"Failed to import GSS pre-authentication context");
ret = _krb5_gss_map_error(major, minor);
} else
ret = 0;
gss_release_buffer(&minor, &sec_context_token);
return ret;
}
/*
* Encode the FX-COOKIE context state, consisting of the exported
* GSS context token concatenated with the checksum of the initial
* KDC-REQ-BODY.
*/
static krb5_error_code
pa_gss_encode_context_state(astgs_request_t r,
gss_const_buffer_t sec_context_token,
const krb5_checksum *req_body_checksum,
krb5_data *state)
{
krb5_error_code ret;
krb5_storage *sp;
krb5_data data;
krb5_data_zero(state);
sp = krb5_storage_emem();
if (sp == NULL) {
ret = krb5_enomem(r->context);
goto out;
}
krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_PACKED);
_krb5_gss_buffer_to_data(sec_context_token, &data);
ret = krb5_store_data(sp, data);
if (ret)
goto out;
ret = krb5_store_int32(sp, req_body_checksum->cksumtype);
if (ret)
goto out;
ret = krb5_store_bytes(sp, req_body_checksum->checksum.data,
req_body_checksum->checksum.length);
if (ret)
goto out;
ret = krb5_storage_to_data(sp, state);
if (ret)
goto out;
out:
krb5_storage_free(sp);
return ret;
}
/*
* Serialize a GSS-API security context into a FAST cookie.
*/
static krb5_error_code
pa_gss_set_context_state(astgs_request_t r,
gss_client_params *gcp)
{
krb5_error_code ret;
PA_DATA *fast_pa;
int idx = 0;
krb5_data state;
OM_uint32 major, minor;
gss_buffer_desc sec_context_token = GSS_C_EMPTY_BUFFER;
/*
* On second and subsequent responses, we can recycle the checksum
* from the request as it is validated and invariant. This saves
* re-encoding the request body again.
*/
if (gcp->req_body_checksum.cksumtype == CKSUMTYPE_NONE) {
ret = pa_gss_create_req_body_checksum(r, &gcp->req_body_checksum);
if (ret)
return ret;
}
major = gss_export_sec_context(&minor, &gcp->context_handle,
&sec_context_token);
if (GSS_ERROR(major)) {
pa_gss_display_status(r, major, minor, gcp,
"Failed to export GSS pre-authentication context");
return _krb5_gss_map_error(major, minor);
}
ret = pa_gss_encode_context_state(r, &sec_context_token,
&gcp->req_body_checksum, &state);
gss_release_buffer(&minor, &sec_context_token);
if (ret)
return ret;
fast_pa = krb5_find_padata(r->fast.fast_state.val,
r->fast.fast_state.len,
KRB5_PADATA_GSS, &idx);
if (fast_pa) {
krb5_data_free(&fast_pa->padata_value);
fast_pa->padata_value = state;
} else {
ret = krb5_padata_add(r->context, &r->fast.fast_state,
KRB5_PADATA_GSS,
state.data, state.length);
if (ret)
krb5_data_free(&state);
}
return ret;
}
static krb5_error_code
pa_gss_acquire_acceptor_cred(astgs_request_t r,
gss_client_params *gcp,
gss_cred_id_t *cred)
{
krb5_error_code ret;
krb5_principal tgs_name;
OM_uint32 major, minor;
gss_name_t target_name = GSS_C_NO_NAME;
gss_buffer_desc display_name = GSS_C_EMPTY_BUFFER;
gss_const_buffer_t display_name_p;
*cred = GSS_C_NO_CREDENTIAL;
ret = krb5_make_principal(r->context, &tgs_name, r->req.req_body.realm,
KRB5_TGS_NAME, r->req.req_body.realm, NULL);
if (ret)
return ret;
ret = _krb5_gss_pa_unparse_name(r->context, tgs_name, &target_name);
krb5_free_principal(r->context, tgs_name);
if (ret)
return ret;
pa_gss_display_name(target_name, &display_name, &display_name_p);
kdc_log(r->context, r->config, 4,
"Acquiring GSS acceptor credential for %.*s",
(int)display_name_p->length, (char *)display_name_p->value);
major = gss_acquire_cred(&minor, target_name, GSS_C_INDEFINITE,
r->config->gss_mechanisms_allowed,
GSS_C_ACCEPT, cred, NULL, NULL);
ret = _krb5_gss_map_error(major, minor);
if (ret)
pa_gss_display_status(r, major, minor, gcp,
"Failed to acquire GSS acceptor credential");
gss_release_buffer(&minor, &display_name);
gss_release_name(&minor, &target_name);
return ret;
}
krb5_error_code
_kdc_gss_rd_padata(astgs_request_t r,
const PA_DATA *pa,
gss_client_params **pgcp,
int *open)
{
krb5_error_code ret;
OM_uint32 minor;
gss_client_params *gcp = NULL;
gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
struct gss_channel_bindings_struct cb;
memset(&cb, 0, sizeof(cb));
*pgcp = NULL;
if (!r->config->enable_gss_preauth) {
ret = KRB5KDC_ERR_POLICY;
goto out;
}
if (pa->padata_value.length == 0) {
ret = KRB5KDC_ERR_PREAUTH_FAILED;
goto out;
}
gcp = calloc(1, sizeof(*gcp));
if (gcp == NULL) {
ret = krb5_enomem(r->context);
goto out;
}
/* errors are fast fail until gss_accept_sec_context() is called */
gcp->major = GSS_S_NO_CONTEXT;
ret = pa_gss_get_context_state(r, gcp);
if (ret)
goto out;
ret = pa_gss_acquire_acceptor_cred(r, gcp, &cred);
if (ret)
goto out;
_krb5_gss_data_to_buffer(&pa->padata_value, &input_token);
_krb5_gss_data_to_buffer(&r->req.req_body._save, &cb.application_data);
gcp->major = gss_accept_sec_context(&gcp->minor,
&gcp->context_handle,
cred,
&input_token,
&cb,
&gcp->initiator_name,
&gcp->mech_type,
&gcp->output_token,
&gcp->flags,
&gcp->lifetime,
NULL); /* delegated_cred_handle */
ret = _krb5_gss_map_error(gcp->major, gcp->minor);
if (GSS_ERROR(gcp->major)) {
pa_gss_display_status(r, gcp->major, gcp->minor, gcp,
"Failed to accept GSS security context");
} else if ((gcp->flags & GSS_C_ANON_FLAG) && !_kdc_is_anon_request(&r->req)) {
kdc_log(r->context, r->config, 2,
"Anonymous GSS pre-authentication request w/o anonymous flag");
ret = KRB5KDC_ERR_BADOPTION;
} else
*open = (gcp->major == GSS_S_COMPLETE);
out:
gss_release_cred(&minor, &cred);
if (gcp && gcp->major != GSS_S_NO_CONTEXT)
*pgcp = gcp;
else
_kdc_gss_free_client_param(r, gcp);
return ret;
}
krb5_timestamp
_kdc_gss_endtime(astgs_request_t r,
gss_client_params *gcp)
{
krb5_timestamp endtime;
if (gcp->lifetime == GSS_C_INDEFINITE)
endtime = 0;
else
endtime = kdc_time + gcp->lifetime;
kdc_log(r->context, r->config, 10,
"GSS pre-authentication endtime is %ld", endtime);
return endtime;
}
struct pa_gss_authorize_plugin_ctx {
astgs_request_t r;
struct gss_client_params *gcp;
krb5_boolean authorized;
krb5_principal initiator_princ;
krb5_data pac_data;
};
static krb5_error_code KRB5_LIB_CALL
pa_gss_authorize_cb(krb5_context context,
const void *plug,
void *plugctx,
void *userctx)
{
const krb5plugin_gss_preauth_authorizer_ftable *authorizer = plug;
struct pa_gss_authorize_plugin_ctx *pa_gss_authorize_plugin_ctx = userctx;
return authorizer->authorize(plugctx,
pa_gss_authorize_plugin_ctx->r,
pa_gss_authorize_plugin_ctx->gcp->initiator_name,
pa_gss_authorize_plugin_ctx->gcp->mech_type,
pa_gss_authorize_plugin_ctx->gcp->flags,
&pa_gss_authorize_plugin_ctx->authorized,
&pa_gss_authorize_plugin_ctx->initiator_princ,
&pa_gss_authorize_plugin_ctx->pac_data);
}
static const char *plugin_deps[] = {
"kdc",
"hdb",
"gssapi",
"krb5",
NULL
};
static struct heim_plugin_data
gss_preauth_authorizer_data = {
"kdc",
KDC_GSS_PREAUTH_AUTHORIZER,
KDC_GSS_PREAUTH_AUTHORIZER_VERSION_1,
plugin_deps,
kdc_get_instance
};
static krb5_error_code
pa_gss_authorize_plugin(astgs_request_t r,
struct gss_client_params *gcp,
gss_const_buffer_t display_name,
krb5_boolean *authorized,
krb5_principal *initiator_princ,
krb5_data *pac_data)
{
krb5_error_code ret;
struct pa_gss_authorize_plugin_ctx ctx;
ctx.r = r;
ctx.gcp = gcp;
ctx.authorized = 0;
ctx.initiator_princ = NULL;
krb5_data_zero(&ctx.pac_data);
krb5_clear_error_message(r->context);
ret = _krb5_plugin_run_f(r->context, &gss_preauth_authorizer_data,
0, &ctx, pa_gss_authorize_cb);
if (ret != KRB5_PLUGIN_NO_HANDLE) {
const char *msg = krb5_get_error_message(r->context, ret);
kdc_log(r->context, r->config, 7,
"GSS authz plugin %sauthorize%s %s initiator %.*s: %s",
ctx.authorized ? "" : "did not " ,
ctx.authorized ? "d" : "",
gss_oid_to_name(gcp->mech_type),
(int)display_name->length, (char *)display_name->value,
msg);
krb5_free_error_message(r->context, msg);
}
*authorized = ctx.authorized;
*initiator_princ = ctx.initiator_princ;
*pac_data = ctx.pac_data;
return ret;
}
static krb5_error_code
pa_gss_authorize_default(astgs_request_t r,
struct gss_client_params *gcp,
gss_const_buffer_t display_name,
krb5_boolean *authorized,
krb5_principal *initiator_princ,
krb5_data *pac_data)
{
krb5_error_code ret;
krb5_principal principal;
krb5_const_realm realm = r->server->entry.principal->realm;
int flags = 0, cross_realm_allowed = 0, unauth_anon;
/*
* gss_cross_realm_mechanisms_allowed is a list of GSS-API mechanisms
* that are allowed to map directly to Kerberos principals in any
* realm. If the authenticating mechanism is not on the list, then
* the initiator will be mapped to an enterprise principal in the
* service realm. This is useful to stop synthetic principals in
* foreign realms being conflated with true cross-realm principals.
*/
if (r->config->gss_cross_realm_mechanisms_allowed) {
OM_uint32 minor;
gss_test_oid_set_member(&minor, gcp->mech_type,
r->config->gss_cross_realm_mechanisms_allowed,
&cross_realm_allowed);
}
kdc_log(r->context, r->config, 10,
"Initiator %.*s will be mapped to %s",
(int)display_name->length, (char *)display_name->value,
cross_realm_allowed ? "nt-principal" : "nt-enterprise-principal");
if (!cross_realm_allowed)
flags |= KRB5_PRINCIPAL_PARSE_ENTERPRISE | KRB5_PRINCIPAL_PARSE_NO_REALM;
ret = _krb5_gss_pa_parse_name(r->context, gcp->initiator_name,
flags, &principal);
if (ret) {
const char *msg = krb5_get_error_message(r->context, ret);
kdc_log(r->context, r->config, 2,
"Failed to parse %s initiator name %.*s: %s",
gss_oid_to_name(gcp->mech_type),
(int)display_name->length, (char *)display_name->value, msg);
krb5_free_error_message(r->context, msg);
return ret;
}
/*
* GSS_C_ANON_FLAG indicates the client requested anonymous authentication
* (it is validated against the request-anonymous flag).
*
* _kdc_is_anonymous_pkinit() returns TRUE if the principal contains both
* the well known anonymous name and realm.
*/
unauth_anon = (gcp->flags & GSS_C_ANON_FLAG) &&
_kdc_is_anonymous_pkinit(r->context, principal);
/*
* Always use the anonymous entry created in our HDB, i.e. with the local
* realm, for authorizing anonymous requests. This matches PKINIT behavior
* as anonymous PKINIT requests include the KDC realm in the request.
*/
if (unauth_anon || (flags & KRB5_PRINCIPAL_PARSE_ENTERPRISE)) {
ret = krb5_principal_set_realm(r->context, principal, realm);
if (ret) {
krb5_free_principal(r->context, principal);
return ret;
}
}
if (unauth_anon) {
/*
* Special case to avoid changing _kdc_as_rep(). If the initiator is
* the unauthenticated anonymous principal, r->client_princ also needs
* to be set in order to force the AS-REP realm to be set to the well-
* known anonymous identity. This is because (unlike anonymous PKINIT)
* we only require the anonymous flag, not the anonymous name, in the
* client AS-REQ.
*/
krb5_principal anon_princ;
ret = krb5_copy_principal(r->context, principal, &anon_princ);
if (ret)
return ret;
krb5_free_principal(r->context, r->client_princ);
r->client_princ = anon_princ;
}
*authorized = TRUE;
*initiator_princ = principal;
return 0;
}
krb5_error_code
_kdc_gss_check_client(astgs_request_t r,
gss_client_params *gcp,
char **client_name)
{
krb5_error_code ret;
krb5_principal initiator_princ = NULL;
hdb_entry_ex *initiator = NULL;
krb5_boolean authorized = FALSE;
krb5_data pac_data;
OM_uint32 minor;
gss_buffer_desc display_name = GSS_C_EMPTY_BUFFER;
gss_const_buffer_t display_name_p;
*client_name = NULL;
krb5_data_zero(&pac_data);
pa_gss_display_name(gcp->initiator_name, &display_name, &display_name_p);
/*
* If no plugins handled the authorization request, then all clients
* are authorized as the directly corresponding Kerberos principal.
*/
ret = pa_gss_authorize_plugin(r, gcp, display_name_p,
&authorized, &initiator_princ, &pac_data);
if (ret == KRB5_PLUGIN_NO_HANDLE)
ret = pa_gss_authorize_default(r, gcp, display_name_p,
&authorized, &initiator_princ, &pac_data);
if (ret == 0 && !authorized)
ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
if (ret)
goto out;
ret = krb5_unparse_name(r->context, initiator_princ, client_name);
if (ret)
goto out;
kdc_log(r->context, r->config, 4,
"Mapped GSS %s initiator %.*s to principal %s",
gss_oid_to_name(gcp->mech_type),
(int)display_name_p->length, (char *)display_name_p->value,
*client_name);
ret = _kdc_db_fetch(r->context,
r->config,
initiator_princ,
HDB_F_FOR_AS_REQ | HDB_F_GET_CLIENT |
HDB_F_CANON | HDB_F_SYNTHETIC_OK,
NULL,
&r->clientdb,
&initiator);
if (ret) {
const char *msg = krb5_get_error_message(r->context, ret);
kdc_log(r->context, r->config, 4, "UNKNOWN -- %s: %s",
*client_name, msg);
krb5_free_error_message(r->context, msg);
goto out;
}
/*
* If the AS-REQ client name was the well-known federated name, then
* replace the client name with the initiator name. Otherwise, the
* two principals must match, noting that GSS pre-authentication is
* for authentication, not general purpose impersonation.
*/
if (krb5_principal_is_federated(r->context, r->client->entry.principal)) {
initiator->entry.flags.force_canonicalize = 1;
_kdc_free_ent(r->context, r->client);
r->client = initiator;
initiator = NULL;
} else if (!krb5_principal_compare(r->context,
r->client->entry.principal,
initiator->entry.principal)) {
kdc_log(r->context, r->config, 2,
"GSS %s initiator %.*s does not match principal %s",
gss_oid_to_name(gcp->mech_type),
(int)display_name_p->length, (char *)display_name_p->value,
r->cname);
ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
goto out;
}
gcp->pac_data = pac_data;
krb5_data_zero(&pac_data);
out:
krb5_free_principal(r->context, initiator_princ);
if (initiator)
_kdc_free_ent(r->context, initiator);
krb5_data_free(&pac_data);
gss_release_buffer(&minor, &display_name);
return ret;
}
krb5_error_code
_kdc_gss_mk_pa_reply(astgs_request_t r,
gss_client_params *gcp)
{
krb5_error_code ret;
const KDC_REQ *req = &r->req;
if (gcp->major == GSS_S_COMPLETE) {
krb5_enctype enctype;
uint32_t kfe = 0;
krb5_keyblock *reply_key = NULL;
if (krb5_principal_is_krbtgt(r->context, r->server_princ))
kfe |= KFE_IS_TGS;
ret = _kdc_find_etype(r, kfe, req->req_body.etype.val,
req->req_body.etype.len, &enctype, NULL, NULL);
if (ret)
return ret;
ret = _krb5_gss_pa_derive_key(r->context, gcp->context_handle,
req->req_body.nonce,
enctype, &reply_key);
if (ret) {
kdc_log(r->context, r->config, 10,
"Failed to derive GSS reply key: %d", ret);
return ret;
}
krb5_free_keyblock_contents(r->context, &r->reply_key);
r->reply_key = *reply_key;
free(reply_key);
} else if (gcp->major == GSS_S_CONTINUE_NEEDED) {
ret = pa_gss_set_context_state(r, gcp);
if (ret)
return ret;
}
/* only return padata in error case if we have an error token */
if (!GSS_ERROR(gcp->major) || gcp->output_token.length) {
ret = krb5_padata_add(r->context, r->rep.padata, KRB5_PADATA_GSS,
gcp->output_token.value, gcp->output_token.length);
if (ret)
return ret;
/* token is now owned by r->rep.padata */
gcp->output_token.length = 0;
gcp->output_token.value = NULL;
}
if (gcp->major == GSS_S_CONTINUE_NEEDED)
ret = KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED;
else
ret = _krb5_gss_map_error(gcp->major, gcp->minor);
return ret;
}
krb5_error_code
_kdc_gss_mk_composite_name_ad(astgs_request_t r,
gss_client_params *gcp)
{
krb5_error_code ret;
krb5_data data;
OM_uint32 major, minor;
gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
if (!r->config->enable_gss_auth_data || (gcp->flags & GSS_C_ANON_FLAG))
return 0;
major = gss_export_name_composite(&minor, gcp->initiator_name, &namebuf);
if (major == GSS_S_COMPLETE) {
_krb5_gss_buffer_to_data(&namebuf, &data);
ret = _kdc_tkt_add_if_relevant_ad(r->context, &r->et,
KRB5_AUTHDATA_GSS_COMPOSITE_NAME,
&data);
} else if (major != GSS_S_UNAVAILABLE)
ret = _krb5_gss_map_error(major, minor);
else
ret = 0;
gss_release_buffer(&minor, &namebuf);
return ret;
}
void
_kdc_gss_free_client_param(astgs_request_t r,
gss_client_params *gcp)
{
OM_uint32 minor;
if (gcp == NULL)
return;
gss_delete_sec_context(&minor, &gcp->context_handle, GSS_C_NO_BUFFER);
gss_release_name(&minor, &gcp->initiator_name);
gss_release_buffer(&minor, &gcp->output_token);
free_Checksum(&gcp->req_body_checksum);
krb5_data_free(&gcp->pac_data);
memset(gcp, 0, sizeof(*gcp));
free(gcp);
}
krb5_error_code
_kdc_gss_get_mechanism_config(krb5_context context,
const char *section,
const char *key,
gss_OID_set *oidsp)
{
krb5_error_code ret;
char **mechs, **mechp;
gss_OID_set oids = GSS_C_NO_OID_SET;
OM_uint32 major, minor;
mechs = krb5_config_get_strings(context, NULL, section, key, NULL);
if (mechs == NULL)
return 0;
major = gss_create_empty_oid_set(&minor, &oids);
if (GSS_ERROR(major)) {
krb5_config_free_strings(mechs);
return _krb5_gss_map_error(major, minor);
}
for (mechp = mechs; *mechp; mechp++) {
gss_OID oid = gss_name_to_oid(*mechp);
if (oid == GSS_C_NO_OID)
continue;
major = gss_add_oid_set_member(&minor, oid, &oids);
if (GSS_ERROR(major))
break;
}
ret = _krb5_gss_map_error(major, minor);
if (ret == 0)
*oidsp = oids;
else
gss_release_oid_set(&minor, &oids);
krb5_config_free_strings(mechs);
return ret;
}
static void
pa_gss_display_status(astgs_request_t r,
OM_uint32 major,
OM_uint32 minor,
gss_client_params *gcp,
const char *msg)
{
krb5_error_code ret = _krb5_gss_map_error(major, minor);
gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
OM_uint32 dmaj, dmin;
OM_uint32 more = 0;
char *gmmsg = NULL;
char *gmsg = NULL;
char *s = NULL;
do {
gss_release_buffer(&dmin, &buf);
dmaj = gss_display_status(&dmin, major, GSS_C_GSS_CODE, GSS_C_NO_OID,
&more, &buf);
if (GSS_ERROR(dmaj) ||
buf.length >= INT_MAX ||
asprintf(&s, "%s%s%.*s", gmsg ? gmsg : "", gmsg ? ": " : "",
(int)buf.length, (char *)buf.value) == -1 ||
s == NULL) {
free(gmsg);
gmsg = NULL;
break;
}
gmsg = s;
s = NULL;
} while (!GSS_ERROR(dmaj) && more);
if (gcp->mech_type != GSS_C_NO_OID) {
do {
gss_release_buffer(&dmin, &buf);
dmaj = gss_display_status(&dmin, major, GSS_C_MECH_CODE,
gcp->mech_type, &more, &buf);
if (GSS_ERROR(dmaj) ||
asprintf(&s, "%s%s%.*s", gmmsg ? gmmsg : "", gmmsg ? ": " : "",
(int)buf.length, (char *)buf.value) == -1 ||
s == NULL) {
free(gmmsg);
gmmsg = NULL;
break;
}
gmmsg = s;
s = NULL;
} while (!GSS_ERROR(dmaj) && more);
}
if (gmsg == NULL)
krb5_set_error_message(r->context, ENOMEM,
"Error displaying GSS-API status");
else
krb5_set_error_message(r->context, ret, "%s%s%s%s", gmsg,
gmmsg ? " (" : "", gmmsg ? gmmsg : "",
gmmsg ? ")" : "");
krb5_prepend_error_message(r->context, ret, "%s", msg);
kdc_log(r->context, r->config, 1,
"%s: %s%s%s%s",
msg, gmsg, gmmsg ? " (" : "", gmmsg ? gmmsg : "",
gmmsg ? ")" : "");
free(gmmsg);
free(gmsg);
}
static const gss_buffer_desc
gss_pa_unknown_display_name = {
sizeof("<unknown name>") - 1,
"<unknown name>"
};
static void
pa_gss_display_name(gss_name_t name,
gss_buffer_t namebuf,
gss_const_buffer_t *namebuf_p)
{
OM_uint32 major, minor;
major = gss_display_name(&minor, name, namebuf, NULL);
if (GSS_ERROR(major))
*namebuf_p = &gss_pa_unknown_display_name;
else
*namebuf_p = namebuf;
}
struct pa_gss_finalize_pac_plugin_ctx {
astgs_request_t r;
krb5_data *pac_data;
};
static krb5_error_code KRB5_LIB_CALL
pa_gss_finalize_pac_cb(krb5_context context,
const void *plug,
void *plugctx,
void *userctx)
{
const krb5plugin_gss_preauth_authorizer_ftable *authorizer = plug;
struct pa_gss_finalize_pac_plugin_ctx *pa_gss_finalize_pac_ctx = userctx;
return authorizer->finalize_pac(plugctx,
pa_gss_finalize_pac_ctx->r,
pa_gss_finalize_pac_ctx->pac_data);
}
krb5_error_code
_kdc_gss_finalize_pac(astgs_request_t r,
gss_client_params *gcp)
{
krb5_error_code ret;
struct pa_gss_finalize_pac_plugin_ctx ctx;
ctx.r = r;
ctx.pac_data = &gcp->pac_data;
krb5_clear_error_message(r->context);
ret = _krb5_plugin_run_f(r->context, &gss_preauth_authorizer_data,
0, &ctx, pa_gss_finalize_pac_cb);
if (ret == KRB5_PLUGIN_NO_HANDLE)
ret = 0;
return ret;
}