summaryrefslogtreecommitdiff
path: root/testcode/unitverify.c
diff options
context:
space:
mode:
Diffstat (limited to 'testcode/unitverify.c')
-rw-r--r--testcode/unitverify.c531
1 files changed, 531 insertions, 0 deletions
diff --git a/testcode/unitverify.c b/testcode/unitverify.c
new file mode 100644
index 000000000000..2bc842c75374
--- /dev/null
+++ b/testcode/unitverify.c
@@ -0,0 +1,531 @@
+/*
+ * testcode/unitverify.c - unit test for signature verification routines.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Calls verification unit tests. Exits with code 1 on a failure.
+ */
+
+#include "config.h"
+#include "util/log.h"
+#include "testcode/unitmain.h"
+#include "validator/val_sigcrypt.h"
+#include "validator/val_nsec.h"
+#include "validator/val_nsec3.h"
+#include "validator/validator.h"
+#include "testcode/ldns-testpkts.h"
+#include "util/data/msgreply.h"
+#include "util/data/msgparse.h"
+#include "util/data/dname.h"
+#include "util/regional.h"
+#include "util/alloc.h"
+#include "util/rbtree.h"
+#include "util/net_help.h"
+#include "util/module.h"
+#include "util/config_file.h"
+
+/** verbose signature test */
+static int vsig = 0;
+
+/** entry to packet buffer with wireformat */
+static void
+entry_to_buf(struct entry* e, ldns_buffer* pkt)
+{
+ unit_assert(e->reply_list);
+ if(e->reply_list->reply_from_hex) {
+ ldns_buffer_copy(pkt, e->reply_list->reply_from_hex);
+ } else {
+ ldns_status status;
+ size_t answer_size;
+ uint8_t* ans = NULL;
+ status = ldns_pkt2wire(&ans, e->reply_list->reply,
+ &answer_size);
+ if(status != LDNS_STATUS_OK) {
+ log_err("could not create reply: %s",
+ ldns_get_errorstr_by_id(status));
+ fatal_exit("error in test");
+ }
+ ldns_buffer_clear(pkt);
+ ldns_buffer_write(pkt, ans, answer_size);
+ ldns_buffer_flip(pkt);
+ free(ans);
+ }
+}
+
+/** entry to reply info conversion */
+static void
+entry_to_repinfo(struct entry* e, struct alloc_cache* alloc,
+ struct regional* region, ldns_buffer* pkt, struct query_info* qi,
+ struct reply_info** rep)
+{
+ int ret;
+ struct edns_data edns;
+ entry_to_buf(e, pkt);
+ /* lock alloc lock to please lock checking software.
+ * alloc_special_obtain assumes it is talking to a ub-alloc,
+ * and does not need to perform locking. Here the alloc is
+ * the only one, so we lock it here */
+ lock_quick_lock(&alloc->lock);
+ ret = reply_info_parse(pkt, alloc, qi, rep, region, &edns);
+ lock_quick_unlock(&alloc->lock);
+ if(ret != 0) {
+ printf("parse code %d: %s\n", ret,
+ ldns_lookup_by_id(ldns_rcodes, ret)->name);
+ unit_assert(ret != 0);
+ }
+}
+
+/** extract DNSKEY rrset from answer and convert it */
+static struct ub_packed_rrset_key*
+extract_keys(struct entry* e, struct alloc_cache* alloc,
+ struct regional* region, ldns_buffer* pkt)
+{
+ struct ub_packed_rrset_key* dnskey = NULL;
+ struct query_info qinfo;
+ struct reply_info* rep = NULL;
+ size_t i;
+
+ entry_to_repinfo(e, alloc, region, pkt, &qinfo, &rep);
+ for(i=0; i<rep->an_numrrsets; i++) {
+ if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_DNSKEY) {
+ dnskey = rep->rrsets[i];
+ rep->rrsets[i] = NULL;
+ break;
+ }
+ }
+ unit_assert(dnskey);
+
+ reply_info_parsedelete(rep, alloc);
+ query_info_clear(&qinfo);
+ return dnskey;
+}
+
+/** return true if answer should be bogus */
+static int
+should_be_bogus(struct ub_packed_rrset_key* rrset, struct query_info* qinfo)
+{
+ struct packed_rrset_data* d = (struct packed_rrset_data*)rrset->
+ entry.data;
+ if(d->rrsig_count == 0)
+ return 1;
+ /* name 'bogus' as first label signals bogus */
+ if(rrset->rk.dname_len > 6 && memcmp(rrset->rk.dname+1, "bogus", 5)==0)
+ return 1;
+ if(qinfo->qname_len > 6 && memcmp(qinfo->qname+1, "bogus", 5)==0)
+ return 1;
+ return 0;
+}
+
+/** return number of rrs in an rrset */
+static size_t
+rrset_get_count(struct ub_packed_rrset_key* rrset)
+{
+ struct packed_rrset_data* d = (struct packed_rrset_data*)
+ rrset->entry.data;
+ if(!d) return 0;
+ return d->count;
+}
+
+/** setup sig alg list from dnskey */
+static void
+setup_sigalg(struct ub_packed_rrset_key* dnskey, uint8_t* sigalg)
+{
+ uint8_t a[ALGO_NEEDS_MAX];
+ size_t i, n = 0;
+ memset(a, 0, sizeof(a));
+ for(i=0; i<rrset_get_count(dnskey); i++) {
+ uint8_t algo = (uint8_t)dnskey_get_algo(dnskey, i);
+ if(a[algo] == 0) {
+ a[algo] = 1;
+ sigalg[n++] = algo;
+ }
+ }
+ sigalg[n] = 0;
+}
+
+/** verify and test one rrset against the key rrset */
+static void
+verifytest_rrset(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey,
+ struct query_info* qinfo)
+{
+ enum sec_status sec;
+ char* reason = NULL;
+ uint8_t sigalg[ALGO_NEEDS_MAX+1];
+ if(vsig) {
+ log_nametypeclass(VERB_QUERY, "verify of rrset",
+ rrset->rk.dname, ntohs(rrset->rk.type),
+ ntohs(rrset->rk.rrset_class));
+ }
+ setup_sigalg(dnskey, sigalg); /* check all algorithms in the dnskey */
+ sec = dnskeyset_verify_rrset(env, ve, rrset, dnskey, sigalg, &reason);
+ if(vsig) {
+ printf("verify outcome is: %s %s\n", sec_status_to_string(sec),
+ reason?reason:"");
+ }
+ if(should_be_bogus(rrset, qinfo)) {
+ unit_assert(sec == sec_status_bogus);
+ } else {
+ unit_assert(sec == sec_status_secure);
+ }
+}
+
+/** verify and test an entry - every rr in the message */
+static void
+verifytest_entry(struct entry* e, struct alloc_cache* alloc,
+ struct regional* region, ldns_buffer* pkt,
+ struct ub_packed_rrset_key* dnskey, struct module_env* env,
+ struct val_env* ve)
+{
+ struct query_info qinfo;
+ struct reply_info* rep = NULL;
+ size_t i;
+
+ regional_free_all(region);
+ if(vsig) {
+ printf("verifying pkt:\n");
+ ldns_pkt_print(stdout, e->reply_list->reply);
+ printf("\n");
+ }
+ entry_to_repinfo(e, alloc, region, pkt, &qinfo, &rep);
+
+ for(i=0; i<rep->rrset_count; i++) {
+ verifytest_rrset(env, ve, rep->rrsets[i], dnskey, &qinfo);
+ }
+
+ reply_info_parsedelete(rep, alloc);
+ query_info_clear(&qinfo);
+}
+
+/** find RRset in reply by type */
+static struct ub_packed_rrset_key*
+find_rrset_type(struct reply_info* rep, uint16_t type)
+{
+ size_t i;
+ for(i=0; i<rep->rrset_count; i++) {
+ if(ntohs(rep->rrsets[i]->rk.type) == type)
+ return rep->rrsets[i];
+ }
+ return NULL;
+}
+
+/** DS sig test an entry - get DNSKEY and DS in entry and verify */
+static void
+dstest_entry(struct entry* e, struct alloc_cache* alloc,
+ struct regional* region, ldns_buffer* pkt, struct module_env* env)
+{
+ struct query_info qinfo;
+ struct reply_info* rep = NULL;
+ struct ub_packed_rrset_key* ds, *dnskey;
+ int ret;
+
+ regional_free_all(region);
+ if(vsig) {
+ printf("verifying DS-DNSKEY match:\n");
+ ldns_pkt_print(stdout, e->reply_list->reply);
+ printf("\n");
+ }
+ entry_to_repinfo(e, alloc, region, pkt, &qinfo, &rep);
+ ds = find_rrset_type(rep, LDNS_RR_TYPE_DS);
+ dnskey = find_rrset_type(rep, LDNS_RR_TYPE_DNSKEY);
+ /* check test is OK */
+ unit_assert(ds && dnskey);
+
+ ret = ds_digest_match_dnskey(env, dnskey, 0, ds, 0);
+ if(strncmp((char*)qinfo.qname, "\003yes", 4) == 0) {
+ if(vsig) {
+ printf("result(yes)= %s\n", ret?"yes":"no");
+ }
+ unit_assert(ret);
+ } else if (strncmp((char*)qinfo.qname, "\002no", 3) == 0) {
+ if(vsig) {
+ printf("result(no)= %s\n", ret?"yes":"no");
+ }
+ unit_assert(!ret);
+ verbose(VERB_QUERY, "DS fail: OK; matched unit test");
+ } else {
+ fatal_exit("Bad qname in DS unit test, yes or no");
+ }
+
+ reply_info_parsedelete(rep, alloc);
+ query_info_clear(&qinfo);
+}
+
+/** verify from a file */
+static void
+verifytest_file(const char* fname, const char* at_date)
+{
+ /*
+ * The file contains a list of ldns-testpkts entries.
+ * The first entry must be a query for DNSKEY.
+ * The answer rrset is the keyset that will be used for verification
+ */
+ struct ub_packed_rrset_key* dnskey;
+ struct regional* region = regional_create();
+ struct alloc_cache alloc;
+ ldns_buffer* buf = ldns_buffer_new(65535);
+ struct entry* e;
+ struct entry* list = read_datafile(fname);
+ struct module_env env;
+ struct val_env ve;
+ uint32_t now = time(NULL);
+
+ if(!list)
+ fatal_exit("could not read %s: %s", fname, strerror(errno));
+ alloc_init(&alloc, NULL, 1);
+ memset(&env, 0, sizeof(env));
+ memset(&ve, 0, sizeof(ve));
+ env.scratch = region;
+ env.scratch_buffer = buf;
+ env.now = &now;
+ ve.date_override = cfg_convert_timeval(at_date);
+ unit_assert(region && buf);
+ dnskey = extract_keys(list, &alloc, region, buf);
+ if(vsig) log_nametypeclass(VERB_QUERY, "test dnskey",
+ dnskey->rk.dname, ntohs(dnskey->rk.type),
+ ntohs(dnskey->rk.rrset_class));
+ /* ready to go! */
+ for(e = list->next; e; e = e->next) {
+ verifytest_entry(e, &alloc, region, buf, dnskey, &env, &ve);
+ }
+
+ ub_packed_rrset_parsedelete(dnskey, &alloc);
+ delete_entry(list);
+ regional_destroy(region);
+ alloc_clear(&alloc);
+ ldns_buffer_free(buf);
+}
+
+/** verify DS matches DNSKEY from a file */
+static void
+dstest_file(const char* fname)
+{
+ /*
+ * The file contains a list of ldns-testpkts entries.
+ * The first entry must be a query for DNSKEY.
+ * The answer rrset is the keyset that will be used for verification
+ */
+ struct regional* region = regional_create();
+ struct alloc_cache alloc;
+ ldns_buffer* buf = ldns_buffer_new(65535);
+ struct entry* e;
+ struct entry* list = read_datafile(fname);
+ struct module_env env;
+
+ if(!list)
+ fatal_exit("could not read %s: %s", fname, strerror(errno));
+ alloc_init(&alloc, NULL, 1);
+ memset(&env, 0, sizeof(env));
+ env.scratch = region;
+ env.scratch_buffer = buf;
+ unit_assert(region && buf);
+
+ /* ready to go! */
+ for(e = list; e; e = e->next) {
+ dstest_entry(e, &alloc, region, buf, &env);
+ }
+
+ delete_entry(list);
+ regional_destroy(region);
+ alloc_clear(&alloc);
+ ldns_buffer_free(buf);
+}
+
+/** helper for unittest of NSEC routines */
+static int
+unitest_nsec_has_type_rdata(char* bitmap, size_t len, uint16_t type)
+{
+ return nsecbitmap_has_type_rdata((uint8_t*)bitmap, len, type);
+}
+
+/** Test NSEC type bitmap routine */
+static void
+nsectest(void)
+{
+ /* bitmap starts at type bitmap rdata field */
+ /* from rfc 4034 example */
+ char* bitmap = "\000\006\100\001\000\000\000\003"
+ "\004\033\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\040";
+ size_t len = 37;
+
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 0));
+ unit_assert(unitest_nsec_has_type_rdata(bitmap, len, LDNS_RR_TYPE_A));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 2));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 3));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 4));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 5));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 6));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 7));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 8));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 9));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 10));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 11));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 12));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 13));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 14));
+ unit_assert(unitest_nsec_has_type_rdata(bitmap, len, LDNS_RR_TYPE_MX));
+ unit_assert(unitest_nsec_has_type_rdata(bitmap, len, LDNS_RR_TYPE_RRSIG));
+ unit_assert(unitest_nsec_has_type_rdata(bitmap, len, LDNS_RR_TYPE_NSEC));
+ unit_assert(unitest_nsec_has_type_rdata(bitmap, len, 1234));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1233));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1235));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1236));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1237));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1238));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1239));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1240));
+ unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 2230));
+}
+
+/** Test hash algo - NSEC3 hash it and compare result */
+static void
+nsec3_hash_test_entry(struct entry* e, rbtree_t* ct,
+ struct alloc_cache* alloc, struct regional* region,
+ ldns_buffer* buf)
+{
+ struct query_info qinfo;
+ struct reply_info* rep = NULL;
+ struct ub_packed_rrset_key* answer, *nsec3;
+ struct nsec3_cached_hash* hash;
+ int ret;
+ uint8_t* qname;
+
+ if(vsig) {
+ printf("verifying NSEC3 hash:\n");
+ ldns_pkt_print(stdout, e->reply_list->reply);
+ printf("\n");
+ }
+ entry_to_repinfo(e, alloc, region, buf, &qinfo, &rep);
+ nsec3 = find_rrset_type(rep, LDNS_RR_TYPE_NSEC3);
+ answer = find_rrset_type(rep, LDNS_RR_TYPE_AAAA);
+ qname = regional_alloc_init(region, qinfo.qname, qinfo.qname_len);
+ /* check test is OK */
+ unit_assert(nsec3 && answer && qname);
+
+ ret = nsec3_hash_name(ct, region, buf, nsec3, 0, qname,
+ qinfo.qname_len, &hash);
+ if(ret != 1) {
+ printf("Bad nsec3_hash_name retcode %d\n", ret);
+ unit_assert(ret == 1);
+ }
+ unit_assert(hash->dname && hash->hash && hash->hash_len &&
+ hash->b32 && hash->b32_len);
+ unit_assert(hash->b32_len == (size_t)answer->rk.dname[0]);
+ /* does not do lowercasing. */
+ unit_assert(memcmp(hash->b32, answer->rk.dname+1, hash->b32_len)
+ == 0);
+
+ reply_info_parsedelete(rep, alloc);
+ query_info_clear(&qinfo);
+}
+
+
+/** Read file to test NSEC3 hash algo */
+static void
+nsec3_hash_test(const char* fname)
+{
+ /*
+ * The list contains a list of ldns-testpkts entries.
+ * Every entry is a test.
+ * The qname is hashed.
+ * The answer section AAAA RR name is the required result.
+ * The auth section NSEC3 is used to get hash parameters.
+ * The hash cache is maintained per file.
+ *
+ * The test does not perform canonicalization during the compare.
+ */
+ rbtree_t ct;
+ struct regional* region = regional_create();
+ struct alloc_cache alloc;
+ ldns_buffer* buf = ldns_buffer_new(65535);
+ struct entry* e;
+ struct entry* list = read_datafile(fname);
+
+ if(!list)
+ fatal_exit("could not read %s: %s", fname, strerror(errno));
+ rbtree_init(&ct, &nsec3_hash_cmp);
+ alloc_init(&alloc, NULL, 1);
+ unit_assert(region && buf);
+
+ /* ready to go! */
+ for(e = list; e; e = e->next) {
+ nsec3_hash_test_entry(e, &ct, &alloc, region, buf);
+ }
+
+ delete_entry(list);
+ regional_destroy(region);
+ alloc_clear(&alloc);
+ ldns_buffer_free(buf);
+}
+
+void
+verify_test(void)
+{
+ unit_show_feature("signature verify");
+ verifytest_file("testdata/test_signatures.1", "20070818005004");
+ verifytest_file("testdata/test_signatures.2", "20080414005004");
+ verifytest_file("testdata/test_signatures.3", "20080416005004");
+ verifytest_file("testdata/test_signatures.4", "20080416005004");
+ verifytest_file("testdata/test_signatures.5", "20080416005004");
+ verifytest_file("testdata/test_signatures.6", "20080416005004");
+ verifytest_file("testdata/test_signatures.7", "20070829144150");
+ verifytest_file("testdata/test_signatures.8", "20070829144150");
+#if defined(HAVE_EVP_SHA256) && defined(USE_SHA2)
+ verifytest_file("testdata/test_sigs.rsasha256", "20070829144150");
+ verifytest_file("testdata/test_sigs.sha1_and_256", "20070829144150");
+ verifytest_file("testdata/test_sigs.rsasha256_draft", "20090101000000");
+#endif
+#if defined(HAVE_EVP_SHA512) && defined(USE_SHA2)
+ verifytest_file("testdata/test_sigs.rsasha512_draft", "20070829144150");
+#endif
+ verifytest_file("testdata/test_sigs.hinfo", "20090107100022");
+ verifytest_file("testdata/test_sigs.revoked", "20080414005004");
+#ifdef USE_GOST
+ if(ldns_key_EVP_load_gost_id())
+ verifytest_file("testdata/test_sigs.gost", "20090807060504");
+ else printf("Warning: skipped GOST, openssl does not provide gost.\n");
+#endif
+#ifdef USE_ECDSA
+ verifytest_file("testdata/test_sigs.ecdsa_p256", "20100908100439");
+ verifytest_file("testdata/test_sigs.ecdsa_p384", "20100908100439");
+ dstest_file("testdata/test_ds.sha384");
+#endif
+ dstest_file("testdata/test_ds.sha1");
+ nsectest();
+ nsec3_hash_test("testdata/test_nsec3_hash.1");
+}