kdc: validate KDC-REQ-BODY invariance in GSS preauth
Whilst channel bindings are used to bind the KDC-REQ-BODY to the GSS-API context, we need to also bind the KDC-REQ-BODY across multiple requests in a pre-authentication conversation. Do this by making a digest of the first KDC-REQ-BODY (with the nonce zeroed, as this may change), and verifying it in each subsequent request.
This commit is contained in:
@ -50,6 +50,7 @@ struct gss_client_params {
|
||||
gss_buffer_desc output_token;
|
||||
OM_uint32 flags;
|
||||
OM_uint32 lifetime;
|
||||
krb5_checksum req_body_checksum;
|
||||
};
|
||||
|
||||
static void
|
||||
@ -64,6 +65,135 @@ 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 = krb5_verify_checksum(r->context, NULL, 0,
|
||||
data.data, data.length, 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.
|
||||
*/
|
||||
@ -73,25 +203,89 @@ pa_gss_get_context_state(astgs_request_t r,
|
||||
{
|
||||
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) {
|
||||
gss_buffer_desc sec_context_token;
|
||||
OM_uint32 major, minor;
|
||||
if (fast_pa == NULL)
|
||||
return 0;
|
||||
|
||||
_krb5_gss_data_to_buffer(&fast_pa->padata_value, &sec_context_token);
|
||||
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 = pa_gss_decode_context_state(r, &fast_pa->padata_value,
|
||||
&sec_context_token,
|
||||
&gcp->req_body_checksum);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return _krb5_gss_map_error(major, minor);
|
||||
ret = pa_gss_verify_req_body_checksum(r, &gcp->req_body_checksum);
|
||||
if (ret)
|
||||
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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -104,10 +298,22 @@ pa_gss_set_context_state(astgs_request_t r,
|
||||
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)) {
|
||||
@ -116,25 +322,27 @@ pa_gss_set_context_state(astgs_request_t r,
|
||||
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);
|
||||
_krb5_gss_buffer_to_data(&sec_context_token, &fast_pa->padata_value);
|
||||
fast_pa->padata_value = state;
|
||||
} else {
|
||||
ret = krb5_padata_add(r->context,
|
||||
&r->fast.fast_state,
|
||||
ret = krb5_padata_add(r->context, &r->fast.fast_state,
|
||||
KRB5_PADATA_GSS,
|
||||
sec_context_token.value,
|
||||
sec_context_token.length);
|
||||
if (ret) {
|
||||
gss_release_buffer(&minor, &sec_context_token);
|
||||
return ret;
|
||||
}
|
||||
state.data, state.length);
|
||||
if (ret)
|
||||
krb5_data_free(&state);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
@ -656,6 +864,7 @@ _kdc_gss_free_client_param(astgs_request_t r,
|
||||
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);
|
||||
memset(gcp, 0, sizeof(*gcp));
|
||||
free(gcp);
|
||||
}
|
||||
|
Reference in New Issue
Block a user