summaryrefslogtreecommitdiff
path: root/source4/dns_server
diff options
context:
space:
mode:
authorKai Blin <kai@samba.org>2012-09-05 08:34:49 +0200
committerKai Blin <kai@samba.org>2012-09-05 19:02:17 +0200
commit53f602c3744c0952f3385a39d5984d5a47b9905c (patch)
treeed78b6251d3fc979cd493a5a3e3e3036e90f199e /source4/dns_server
parent7fe5e2cdcb17cee06ebde2717439c0aa964ac026 (diff)
downloadsamba-53f602c3744c0952f3385a39d5984d5a47b9905c.tar.gz
s4 dns: Verify incoming TSIG signatures
Diffstat (limited to 'source4/dns_server')
-rw-r--r--source4/dns_server/dns_crypto.c174
-rw-r--r--source4/dns_server/dns_server.c8
-rw-r--r--source4/dns_server/dns_server.h5
-rw-r--r--source4/dns_server/dns_utils.c2
4 files changed, 189 insertions, 0 deletions
diff --git a/source4/dns_server/dns_crypto.c b/source4/dns_server/dns_crypto.c
index 87d82b86a6..14dc4ca69b 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 795b7198aa..d9851b1566 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 0093879451..ef85730ff8 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 7d95bdd151..11ded68204 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;