From 53f602c3744c0952f3385a39d5984d5a47b9905c Mon Sep 17 00:00:00 2001 From: Kai Blin Date: Wed, 5 Sep 2012 08:34:49 +0200 Subject: [PATCH] s4 dns: Verify incoming TSIG signatures --- source4/dns_server/dns_crypto.c | 174 ++++++++++++++++++++++++++++++++ source4/dns_server/dns_server.c | 8 ++ source4/dns_server/dns_server.h | 5 + source4/dns_server/dns_utils.c | 2 + 4 files changed, 189 insertions(+) diff --git a/source4/dns_server/dns_crypto.c b/source4/dns_server/dns_crypto.c index 87d82b86a686..14dc4ca69b5c 100644 --- a/source4/dns_server/dns_crypto.c +++ b/source4/dns_server/dns_crypto.c @@ -29,6 +29,41 @@ #include "auth/auth.h" #include "auth/gensec/gensec.h" +static WERROR dns_copy_tsig(TALLOC_CTX *mem_ctx, + struct dns_res_rec *old, + struct dns_res_rec *new_rec) +{ + new_rec->name = talloc_strdup(mem_ctx, old->name); + W_ERROR_HAVE_NO_MEMORY(new_rec->name); + + new_rec->rr_type = old->rr_type; + new_rec->rr_class = old->rr_class; + new_rec->ttl = old->ttl; + new_rec->length = old->length; + new_rec->rdata.tsig_record.algorithm_name = talloc_strdup(mem_ctx, + old->rdata.tsig_record.algorithm_name); + W_ERROR_HAVE_NO_MEMORY(new_rec->rdata.tsig_record.algorithm_name); + + new_rec->rdata.tsig_record.time_prefix = old->rdata.tsig_record.time_prefix; + new_rec->rdata.tsig_record.time = old->rdata.tsig_record.time; + new_rec->rdata.tsig_record.fudge = old->rdata.tsig_record.fudge; + new_rec->rdata.tsig_record.mac_size = old->rdata.tsig_record.mac_size; + new_rec->rdata.tsig_record.mac = talloc_memdup(mem_ctx, + old->rdata.tsig_record.mac, + old->rdata.tsig_record.mac_size); + W_ERROR_HAVE_NO_MEMORY(new_rec->rdata.tsig_record.mac); + + new_rec->rdata.tsig_record.original_id = old->rdata.tsig_record.original_id; + new_rec->rdata.tsig_record.error = old->rdata.tsig_record.error; + new_rec->rdata.tsig_record.other_size = old->rdata.tsig_record.other_size; + new_rec->rdata.tsig_record.other_data = talloc_memdup(mem_ctx, + old->rdata.tsig_record.other_data, + old->rdata.tsig_record.other_size); + W_ERROR_HAVE_NO_MEMORY(new_rec->rdata.tsig_record.other_data); + + return WERR_OK; +} + struct dns_server_tkey *dns_find_tkey(struct dns_server_tkey_store *store, const char *name) { @@ -53,6 +88,145 @@ struct dns_server_tkey *dns_find_tkey(struct dns_server_tkey_store *store, return tkey; } +WERROR dns_verify_tsig(struct dns_server *dns, + TALLOC_CTX *mem_ctx, + struct dns_request_state *state, + struct dns_name_packet *packet, + DATA_BLOB *in) +{ + WERROR werror; + NTSTATUS status; + enum ndr_err_code ndr_err; + bool found_tsig = false; + uint16_t i, arcount = 0; + DATA_BLOB tsig_blob, fake_tsig_blob, sig; + uint8_t *buffer = NULL; + size_t buffer_len = 0, packet_len = 0; + struct dns_server_tkey *tkey = NULL; + struct dns_fake_tsig_rec *check_rec = talloc_zero(mem_ctx, + struct dns_fake_tsig_rec); + + + /* Find the first TSIG record in the additional records */ + for (i=0; i < packet->arcount; i++) { + if (packet->additional[i].rr_type == DNS_QTYPE_TSIG) { + found_tsig = true; + break; + } + } + + if (!found_tsig) { + return WERR_OK; + } + + /* The TSIG record needs to be the last additional record */ + if (found_tsig && i + 1 != packet->arcount) { + DEBUG(0, ("TSIG record not the last additional record!\n")); + return DNS_ERR(FORMAT_ERROR); + } + + /* We got a TSIG, so we need to sign our reply */ + state->sign = true; + + state->tsig = talloc_zero(mem_ctx, struct dns_res_rec); + if (state->tsig == NULL) { + return WERR_NOMEM; + } + + werror = dns_copy_tsig(state->tsig, &packet->additional[i], + state->tsig); + if (!W_ERROR_IS_OK(werror)) { + return werror; + } + + packet->arcount--; + + tkey = dns_find_tkey(dns->tkeys, state->tsig->name); + if (tkey == NULL) { + state->tsig_error = DNS_RCODE_BADKEY; + return DNS_ERR(NOTAUTH); + } + + /* FIXME: check TSIG here */ + if (check_rec == NULL) { + return WERR_NOMEM; + } + + /* first build and verify check packet */ + check_rec->name = talloc_strdup(check_rec, tkey->name); + if (check_rec->name == NULL) { + return WERR_NOMEM; + } + check_rec->rr_class = DNS_QCLASS_ANY; + check_rec->ttl = 0; + check_rec->algorithm_name = talloc_strdup(check_rec, tkey->algorithm); + if (check_rec->algorithm_name == NULL) { + return WERR_NOMEM; + } + check_rec->time_prefix = 0; + check_rec->time = state->tsig->rdata.tsig_record.time; + check_rec->fudge = state->tsig->rdata.tsig_record.fudge; + check_rec->error = 0; + check_rec->other_size = 0; + check_rec->other_data = NULL; + + ndr_err = ndr_push_struct_blob(&tsig_blob, mem_ctx, state->tsig, + (ndr_push_flags_fn_t)ndr_push_dns_res_rec); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(1, ("Failed to push packet: %s!\n", + ndr_errstr(ndr_err))); + return DNS_ERR(SERVER_FAILURE); + } + + ndr_err = ndr_push_struct_blob(&fake_tsig_blob, mem_ctx, check_rec, + (ndr_push_flags_fn_t)ndr_push_dns_fake_tsig_rec); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(1, ("Failed to push packet: %s!\n", + ndr_errstr(ndr_err))); + return DNS_ERR(SERVER_FAILURE); + } + + /* we need to work some magic here. we need to keep the input packet + * exactly like we got it, but we need to cut off the tsig record */ + packet_len = in->length - tsig_blob.length; + buffer_len = packet_len + fake_tsig_blob.length; + buffer = talloc_zero_array(mem_ctx, uint8_t, buffer_len); + if (buffer == NULL) { + return WERR_NOMEM; + } + + memcpy(buffer, in->data, packet_len); + memcpy(buffer + packet_len, fake_tsig_blob.data, fake_tsig_blob.length); + + sig.length = state->tsig->rdata.tsig_record.mac_size; + sig.data = talloc_memdup(mem_ctx, state->tsig->rdata.tsig_record.mac, sig.length); + if (sig.data == NULL) { + return WERR_NOMEM; + } + + /*FIXME: Why is there too much padding? */ + buffer_len -= 2; + + /* Now we also need to count down the additional record counter */ + arcount = RSVAL(buffer, 10); + RSSVAL(buffer, 10, arcount-1); + + status = gensec_check_packet(tkey->gensec, buffer, buffer_len, + buffer, buffer_len, &sig); + if (NT_STATUS_EQUAL(NT_STATUS_ACCESS_DENIED, status)) { + return DNS_ERR(BADKEY); + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Verifying tsig failed: %s\n", nt_errstr(status))); + return ntstatus_to_werror(status); + } + + state->authenticated = true; + + return WERR_OK; +} + WERROR dns_sign_tsig(struct dns_server *dns, TALLOC_CTX *mem_ctx, struct dns_request_state *state, diff --git a/source4/dns_server/dns_server.c b/source4/dns_server/dns_server.c index 795b7198aa9c..d9851b156692 100644 --- a/source4/dns_server/dns_server.c +++ b/source4/dns_server/dns_server.c @@ -145,6 +145,14 @@ static struct tevent_req *dns_process_send(TALLOC_CTX *mem_ctx, NDR_PRINT_DEBUG(dns_name_packet, &state->in_packet); } + ret = dns_verify_tsig(dns, state, &state->state, &state->in_packet, in); + if (!W_ERROR_IS_OK(ret)) { + DEBUG(0, ("Bailing out early!\n")); + state->dns_err = werr_to_dns_err(ret); + tevent_req_done(req); + return tevent_req_post(req, ev); + } + state->state.flags = state->in_packet.operation; state->state.flags |= DNS_FLAG_REPLY; diff --git a/source4/dns_server/dns_server.h b/source4/dns_server/dns_server.h index 0093879451cc..ef85730ff833 100644 --- a/source4/dns_server/dns_server.h +++ b/source4/dns_server/dns_server.h @@ -109,6 +109,11 @@ WERROR dns_name2dn(struct dns_server *dns, struct ldb_dn **_dn); struct dns_server_tkey *dns_find_tkey(struct dns_server_tkey_store *store, const char *name); +WERROR dns_verify_tsig(struct dns_server *dns, + TALLOC_CTX *mem_ctx, + struct dns_request_state *state, + struct dns_name_packet *packet, + DATA_BLOB *in); WERROR dns_sign_tsig(struct dns_server *dns, TALLOC_CTX *mem_ctx, struct dns_request_state *state, diff --git a/source4/dns_server/dns_utils.c b/source4/dns_server/dns_utils.c index 7d95bdd1518a..11ded6820487 100644 --- a/source4/dns_server/dns_utils.c +++ b/source4/dns_server/dns_utils.c @@ -54,6 +54,8 @@ uint8_t werr_to_dns_err(WERROR werr) return DNS_RCODE_NOTAUTH; } else if (W_ERROR_EQUAL(DNS_ERR(NOTZONE), werr)) { return DNS_RCODE_NOTZONE; + } else if (W_ERROR_EQUAL(DNS_ERR(BADKEY), werr)) { + return DNS_RCODE_BADKEY; } DEBUG(5, ("No mapping exists for %s\n", win_errstr(werr))); return DNS_RCODE_SERVFAIL; -- 2.34.1