From 0ed4d90a7c7df8dbf2af5b057b410fb7e351b2b6 Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Mon, 13 Sep 2021 16:17:18 +1000 Subject: [PATCH] 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. --- kdc/gss_preauth.c | 251 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 230 insertions(+), 21 deletions(-) diff --git a/kdc/gss_preauth.c b/kdc/gss_preauth.c index 7e99c9d07..be9ef9ce0 100644 --- a/kdc/gss_preauth.c +++ b/kdc/gss_preauth.c @@ -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); }