/* ns_verify * Parameters: * statp res stuff * msg received message * msglen length of message * key tsig key used for verifying. * querysig (response), the signature in the query * querysiglen (response), the length of the signature in the query * sig (query), a buffer to hold the signature * siglen (query), input - length of signature buffer * output - length of signature * * Errors: * - bad input (-1) * - invalid dns message (NS_TSIG_ERROR_FORMERR) * - TSIG is not present (NS_TSIG_ERROR_NO_TSIG) * - key doesn't match (-ns_r_badkey) * - TSIG verification fails with BADKEY (-ns_r_badkey) * - TSIG verification fails with BADSIG (-ns_r_badsig) * - TSIG verification fails with BADTIME (-ns_r_badtime) * - TSIG verification succeeds, error set to BAKEY (ns_r_badkey) * - TSIG verification succeeds, error set to BADSIG (ns_r_badsig) * - TSIG verification succeeds, error set to BADTIME (ns_r_badtime) */ isc_result_t ns_verify(u_char *msg, unsigned *msglen, void *k, const u_char *querysig, unsigned querysiglen, u_char *sig, unsigned *siglen, time_t *timesigned, int nostrip) { HEADER *hp = (HEADER *)msg; DST_KEY *key = (DST_KEY *)k; u_char *cp = msg, *eom; char name[MAXDNAME], alg[MAXDNAME]; u_char *recstart, *rdatastart; u_char *sigstart, *otherstart; unsigned n; int error; u_int16_t type, length; u_int16_t fudge, sigfieldlen, id, otherfieldlen; dst_init(); if (msg == NULL || msglen == NULL) return ISC_R_INVALIDARG; eom = msg + *msglen; recstart = ns_find_tsig(msg, eom); if (recstart == NULL) return ISC_R_NO_TSIG; cp = recstart; /* Read the key name. */ n = dn_expand(msg, eom, cp, name, MAXDNAME); if (n < 0) return ISC_R_FORMERR; cp += n; /* Read the type. */ BOUNDS_CHECK(cp, 2*INT16SZ + INT32SZ + INT16SZ); GETSHORT(type, cp); if (type != ns_t_tsig) return ISC_R_NO_TSIG; /* Skip the class and TTL, save the length. */ cp += INT16SZ + INT32SZ; GETSHORT(length, cp); if (eom - cp != length) return ISC_R_FORMERR; /* Read the algorithm name. */ rdatastart = cp; n = dn_expand(msg, eom, cp, alg, MAXDNAME); if (n < 0) return ISC_R_FORMERR; if (ns_samename(alg, NS_TSIG_ALG_HMAC_MD5) != 1) return ISC_R_INVALIDKEY; cp += n; /* Read the time signed and fudge. */ BOUNDS_CHECK(cp, INT16SZ + INT32SZ + INT16SZ); cp += INT16SZ; GETLONG((*timesigned), cp); GETSHORT(fudge, cp); /* Read the signature. */ BOUNDS_CHECK(cp, INT16SZ); GETSHORT(sigfieldlen, cp); BOUNDS_CHECK(cp, sigfieldlen); sigstart = cp; cp += sigfieldlen; /* Read the original id and error. */ BOUNDS_CHECK(cp, 2*INT16SZ); GETSHORT(id, cp); GETSHORT(error, cp); /* Parse the other data. */ BOUNDS_CHECK(cp, INT16SZ); GETSHORT(otherfieldlen, cp); BOUNDS_CHECK(cp, otherfieldlen); otherstart = cp; cp += otherfieldlen; if (cp != eom) return ISC_R_FORMERR; /* Verify that the key used is OK. */ if (key != NULL) { if (key->dk_alg != KEY_HMAC_MD5) return ISC_R_INVALIDKEY; if (error != ns_r_badsig && error != ns_r_badkey) { if (ns_samename(key->dk_key_name, name) != 1) return ISC_R_INVALIDKEY; } } hp->arcount = htons(ntohs(hp->arcount) - 1); /* * Do the verification. */ if (key != NULL && error != ns_r_badsig && error != ns_r_badkey) { void *ctx; u_char buf[MAXDNAME]; /* Digest the query signature, if this is a response. */ dst_verify_data(SIG_MODE_INIT, key, &ctx, NULL, 0, NULL, 0); if (querysiglen > 0 && querysig != NULL) { u_int16_t len_n = htons(querysiglen); dst_verify_data(SIG_MODE_UPDATE, key, &ctx, (u_char *)&len_n, INT16SZ, NULL, 0); dst_verify_data(SIG_MODE_UPDATE, key, &ctx, querysig, querysiglen, NULL, 0); } /* Digest the message. */ dst_verify_data(SIG_MODE_UPDATE, key, &ctx, msg, (unsigned)(recstart - msg), NULL, 0); /* Digest the key name. */ n = ns_name_ntol(recstart, buf, sizeof(buf)); dst_verify_data(SIG_MODE_UPDATE, key, &ctx, buf, n, NULL, 0); /* Digest the class and TTL. */ dst_verify_data(SIG_MODE_UPDATE, key, &ctx, recstart + dn_skipname(recstart, eom) + INT16SZ, INT16SZ + INT32SZ, NULL, 0); /* Digest the algorithm. */ n = ns_name_ntol(rdatastart, buf, sizeof(buf)); dst_verify_data(SIG_MODE_UPDATE, key, &ctx, buf, n, NULL, 0); /* Digest the time signed and fudge. */ dst_verify_data(SIG_MODE_UPDATE, key, &ctx, rdatastart + dn_skipname(rdatastart, eom), INT16SZ + INT32SZ + INT16SZ, NULL, 0); /* Digest the error and other data. */ dst_verify_data(SIG_MODE_UPDATE, key, &ctx, otherstart - INT16SZ - INT16SZ, (unsigned)otherfieldlen + INT16SZ + INT16SZ, NULL, 0); n = dst_verify_data(SIG_MODE_FINAL, key, &ctx, NULL, 0, sigstart, sigfieldlen); if (n < 0) return ISC_R_BADSIG; if (sig != NULL && siglen != NULL) { if (*siglen < sigfieldlen) return ISC_R_NOSPACE; memcpy(sig, sigstart, sigfieldlen); *siglen = sigfieldlen; } } else { if (sigfieldlen > 0) return ISC_R_FORMERR; if (sig != NULL && siglen != NULL) *siglen = 0; } /* Reset the counter, since we still need to check for badtime. */ hp->arcount = htons(ntohs(hp->arcount) + 1); /* Verify the time. */ if (abs((*timesigned) - time(NULL)) > fudge) return ISC_R_BADTIME; if (nostrip == 0) { *msglen = recstart - msg; hp->arcount = htons(ntohs(hp->arcount) - 1); } if (error != NOERROR) return ns_rcode_to_isc (error); return ISC_R_SUCCESS; }
isc_result_t ns_verify_tcp(u_char *msg, unsigned *msglen, ns_tcp_tsig_state *state, int required) { HEADER *hp = (HEADER *)msg; u_char *recstart, *rdatastart, *sigstart; unsigned sigfieldlen, otherfieldlen; u_char *cp, *eom = msg + *msglen, *cp2; char name[MAXDNAME], alg[MAXDNAME]; u_char buf[MAXDNAME]; int n, type, length, fudge, id, error; time_t timesigned; if (msg == NULL || msglen == NULL || state == NULL) return ISC_R_INVALIDARG; state->counter++; if (state->counter == 0) return (ns_verify(msg, msglen, state->key, state->sig, state->siglen, state->sig, &state->siglen, ×igned, 0)); if (state->siglen > 0) { u_int16_t siglen_n = htons(state->siglen); dst_verify_data(SIG_MODE_INIT, state->key, &state->ctx, NULL, 0, NULL, 0); dst_verify_data(SIG_MODE_UPDATE, state->key, &state->ctx, (u_char *)&siglen_n, INT16SZ, NULL, 0); dst_verify_data(SIG_MODE_UPDATE, state->key, &state->ctx, state->sig, state->siglen, NULL, 0); state->siglen = 0; } cp = recstart = ns_find_tsig(msg, eom); if (recstart == NULL) { if (required) return ISC_R_NO_TSIG; dst_verify_data(SIG_MODE_UPDATE, state->key, &state->ctx, msg, *msglen, NULL, 0); return ISC_R_SUCCESS; } hp->arcount = htons(ntohs(hp->arcount) - 1); dst_verify_data(SIG_MODE_UPDATE, state->key, &state->ctx, msg, (unsigned)(recstart - msg), NULL, 0); /* Read the key name. */ n = dn_expand(msg, eom, cp, name, MAXDNAME); if (n < 0) return ISC_R_FORMERR; cp += n; /* Read the type. */ BOUNDS_CHECK(cp, 2*INT16SZ + INT32SZ + INT16SZ); GETSHORT(type, cp); if (type != ns_t_tsig) return ISC_R_NO_TSIG; /* Skip the class and TTL, save the length. */ cp += INT16SZ + INT32SZ; GETSHORT(length, cp); if (eom - cp != length) return ISC_R_FORMERR; /* Read the algorithm name. */ rdatastart = cp; n = dn_expand(msg, eom, cp, alg, MAXDNAME); if (n < 0) return ISC_R_FORMERR; if (ns_samename(alg, NS_TSIG_ALG_HMAC_MD5) != 1) return ISC_R_BADKEY; cp += n; /* Verify that the key used is OK. */ if ((ns_samename(state->key->dk_key_name, name) != 1 || state->key->dk_alg != KEY_HMAC_MD5)) return ISC_R_BADKEY; /* Read the time signed and fudge. */ BOUNDS_CHECK(cp, INT16SZ + INT32SZ + INT16SZ); cp += INT16SZ; GETLONG(timesigned, cp); GETSHORT(fudge, cp); /* Read the signature. */ BOUNDS_CHECK(cp, INT16SZ); GETSHORT(sigfieldlen, cp); BOUNDS_CHECK(cp, sigfieldlen); sigstart = cp; cp += sigfieldlen; /* Read the original id and error. */ BOUNDS_CHECK(cp, 2*INT16SZ); GETSHORT(id, cp); GETSHORT(error, cp); /* Parse the other data. */ BOUNDS_CHECK(cp, INT16SZ); GETSHORT(otherfieldlen, cp); BOUNDS_CHECK(cp, otherfieldlen); cp += otherfieldlen; if (cp != eom) return ISC_R_FORMERR; /* * Do the verification. */ /* Digest the time signed and fudge. */ cp2 = buf; PUTSHORT(0, cp2); /* Top 16 bits of time. */ PUTLONG(timesigned, cp2); PUTSHORT(NS_TSIG_FUDGE, cp2); dst_verify_data(SIG_MODE_UPDATE, state->key, &state->ctx, buf, (unsigned)(cp2 - buf), NULL, 0); n = dst_verify_data(SIG_MODE_FINAL, state->key, &state->ctx, NULL, 0, sigstart, sigfieldlen); if (n < 0) return ISC_R_BADSIG; if (sigfieldlen > sizeof(state->sig)) return ISC_R_BADSIG; if (sigfieldlen > sizeof(state->sig)) return ISC_R_NOSPACE; memcpy(state->sig, sigstart, sigfieldlen); state->siglen = sigfieldlen; /* Verify the time. */ if (abs(timesigned - time(NULL)) > fudge) return ISC_R_BADTIME; *msglen = recstart - msg; if (error != NOERROR) return ns_rcode_to_isc (error); return ISC_R_SUCCESS; }
static int printZone(ns_type xfr, const char *zone, const struct sockaddr_in *sin, ns_tsig_key *key) { static u_char *answer = NULL; static int answerLen = 0; querybuf buf; int msglen, amtToRead, numRead, result, sockFD, len; int count, type, rlen, done, n; int numAnswers, numRecords, soacnt; u_char *cp, tmp[NS_INT16SZ]; char dname[2][NS_MAXDNAME]; enum { NO_ERRORS, ERR_READING_LEN, ERR_READING_MSG, ERR_PRINTING } error; pid_t zpid = -1; u_char *newmsg; int newmsglen; ns_tcp_tsig_state tsig_state; int tsig_ret, tsig_required, tsig_present; switch (xfr) { case ns_t_axfr: case ns_t_zxfr: break; default: fprintf(stderr, ";; %s - transfer type not supported\n", p_type(xfr)); return (ERROR); } /* * Create a query packet for the requested zone name. */ msglen = res_nmkquery(&res, ns_o_query, zone, queryClass, ns_t_axfr, NULL, 0, 0, buf.qb2, sizeof buf); if (msglen < 0) { if (res.options & RES_DEBUG) fprintf(stderr, ";; res_nmkquery failed\n"); return (ERROR); } /* * Sign the message if a key was sent */ if (key == NULL) { newmsg = (u_char *)&buf; newmsglen = msglen; } else { DST_KEY *dstkey; int bufsize, siglen; u_char sig[64]; int ret; /* ns_sign() also calls dst_init(), but there is no harm * doing it twice */ dst_init(); bufsize = msglen + 1024; newmsg = (u_char *) malloc(bufsize); if (newmsg == NULL) { errno = ENOMEM; return (-1); } memcpy(newmsg, (u_char *)&buf, msglen); newmsglen = msglen; if (strcmp(key->alg, NS_TSIG_ALG_HMAC_MD5) != 0) dstkey = NULL; else dstkey = dst_buffer_to_key(key->name, KEY_HMAC_MD5, NS_KEY_TYPE_AUTH_ONLY, NS_KEY_PROT_ANY, key->data, key->len); if (dstkey == NULL) { errno = EINVAL; if (key) free(newmsg); return (-1); } siglen = sizeof(sig); /* newmsglen++; */ ret = ns_sign(newmsg, &newmsglen, bufsize, NOERROR, dstkey, NULL, 0, sig, &siglen, 0); if (ret < 0) { if (key) free (newmsg); if (ret == NS_TSIG_ERROR_NO_SPACE) errno = EMSGSIZE; else if (ret == -1) errno = EINVAL; return (ret); } ns_verify_tcp_init(dstkey, sig, siglen, &tsig_state); } /* * Set up a virtual circuit to the server. */ if ((sockFD = socket(sin->sin_family, SOCK_STREAM, 0)) < 0) { int e = errno; perror(";; socket"); return (e); } switch (sin->sin_family) { case AF_INET: if (bind(sockFD, (struct sockaddr *)&myaddress, sizeof myaddress) < 0){ int e = errno; fprintf(stderr, ";; bind(%s port %u): %s\n", inet_ntoa(myaddress.sin_addr), ntohs(myaddress.sin_port), strerror(e)); (void) close(sockFD); sockFD = -1; return (e); } if (connect(sockFD, (const struct sockaddr *)sin, sizeof *sin) < 0) { int e = errno; perror(";; connect"); (void) close(sockFD); sockFD = -1; return (e); } break; case AF_INET6: if (bind(sockFD, (struct sockaddr *)&myaddress6, sizeof myaddress6) < 0){ int e = errno; char buf[80]; fprintf(stderr, ";; bind(%s port %u): %s\n", inet_ntop(AF_INET6, &myaddress6.sin6_addr, buf, sizeof(buf)), ntohs(myaddress6.sin6_port), strerror(e)); (void) close(sockFD); sockFD = -1; return (e); } if (connect(sockFD, (const struct sockaddr *)sin, sizeof(struct sockaddr_in6)) < 0) { int e = errno; perror(";; connect"); (void) close(sockFD); sockFD = -1; return (e); } break; } /* * Send length & message for zone transfer */ ns_put16(newmsglen, tmp); if (write(sockFD, (char *)tmp, NS_INT16SZ) != NS_INT16SZ || write(sockFD, (char *)newmsg, newmsglen) != newmsglen) { int e = errno; if (key) free (newmsg); perror(";; write"); (void) close(sockFD); sockFD = -1; return (e); } else if (key) free (newmsg); /* * If we're compressing, push a gzip into the pipeline. */ if (xfr == ns_t_zxfr) { enum { rd = 0, wr = 1 }; int z[2]; if (pipe(z) < 0) { int e = errno; perror(";; pipe"); (void) close(sockFD); sockFD = -1; return (e); } zpid = vfork(); if (zpid < 0) { int e = errno; perror(";; fork"); (void) close(sockFD); sockFD = -1; return (e); } else if (zpid == 0) { /* Child. */ (void) close(z[rd]); (void) dup2(sockFD, STDIN_FILENO); (void) close(sockFD); (void) dup2(z[wr], STDOUT_FILENO); (void) close(z[wr]); execlp("gzip", "gzip", "-d", "-v", NULL); perror(";; child: execlp(gunzip)"); _exit(1); } /* Parent. */ (void) close(z[wr]); (void) dup2(z[rd], sockFD); (void) close(z[rd]); } result = 0; numAnswers = 0; numRecords = 0; soacnt = 0; error = NO_ERRORS; numRead = 0; dname[0][0] = '\0'; for (done = 0; !done; (void)NULL) { /* * Read the length of the response. */ cp = tmp; amtToRead = INT16SZ; while (amtToRead > 0 && (numRead = read(sockFD, cp, amtToRead)) > 0) { cp += numRead; amtToRead -= numRead; } if (numRead <= 0) { error = ERR_READING_LEN; break; } len = ns_get16(tmp); if (len == 0) break; /* nothing left to read */ /* * The server sent too much data to fit the existing buffer -- * allocate a new one. */ if (len > answerLen) { if (answerLen != 0) free(answer); answerLen = len; answer = (u_char *)malloc(answerLen); } /* * Read the response. */ amtToRead = len; cp = answer; while (amtToRead > 0 && (numRead = read(sockFD, cp, amtToRead)) > 0) { cp += numRead; amtToRead -= numRead; } if (numRead <= 0) { error = ERR_READING_MSG; break; } result = print_axfr(stdout, answer, len); if (result != 0) { error = ERR_PRINTING; break; } numRecords += htons(((HEADER *)answer)->ancount); numAnswers++; /* Header. */ cp = answer + HFIXEDSZ; /* Question. */ for (count = ntohs(((HEADER *)answer)->qdcount); count > 0; count--) { n = dn_skipname(cp, answer + len); if (n < 0) { error = ERR_PRINTING; done++; break; } cp += n + QFIXEDSZ; if (cp > answer + len) { error = ERR_PRINTING; done++; break; } } /* Answer. */ for (count = ntohs(((HEADER *)answer)->ancount); count > 0 && !done; count--) { n = dn_expand(answer, answer + len, cp, dname[soacnt], sizeof dname[0]); if (n < 0) { error = ERR_PRINTING; done++; break; } cp += n; if (cp + 3 * INT16SZ + INT32SZ > answer + len) { error = ERR_PRINTING; done++; break; } GETSHORT(type, cp); cp += INT16SZ; cp += INT32SZ; /* ttl */ GETSHORT(rlen, cp); cp += rlen; if (cp > answer + len) { error = ERR_PRINTING; done++; break; } if (type == T_SOA && soacnt++ && ns_samename(dname[0], dname[1]) == 1) { done++; break; } } /* * Verify the TSIG */ if (key) { if (ns_find_tsig(answer, answer + len) != NULL) tsig_present = 1; else tsig_present = 0; if (numAnswers == 1 || soacnt > 1) tsig_required = 1; else tsig_required = 0; tsig_ret = ns_verify_tcp(answer, &len, &tsig_state, tsig_required); if (tsig_ret == 0) { if (tsig_present) printf("; TSIG ok\n"); } else printf("; TSIG invalid\n"); } } printf(";; Received %d answer%s (%d record%s).\n", numAnswers, (numAnswers != 1) ? "s" : "", numRecords, (numRecords != 1) ? "s" : ""); (void) close(sockFD); sockFD = -1; /* * If we were uncompressing, reap the uncompressor. */ if (xfr == ns_t_zxfr) { pid_t pid; int status = 0; pid = wait(&status); if (pid < 0) { int e = errno; perror(";; wait"); return (e); } if (pid != zpid) { fprintf(stderr, ";; wrong pid (%lu != %lu)\n", (u_long)pid, (u_long)zpid); return (ERROR); } printf(";; pid %lu: exit %d, signal %d, core %c\n", (u_long)pid, WEXITSTATUS(status), WIFSIGNALED(status) ? WTERMSIG(status) : 0, WCOREDUMP(status) ? 't' : 'f'); } switch (error) { case NO_ERRORS: return (0); case ERR_READING_LEN: return (EMSGSIZE); case ERR_PRINTING: return (result); case ERR_READING_MSG: return (EMSGSIZE); default: return (EFAULT); } }