1 /* $NetBSD: sigs_test.c,v 1.4 2025/01/26 16:25:48 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 #include <inttypes.h> 17 #include <sched.h> /* IWYU pragma: keep */ 18 #include <setjmp.h> 19 #include <stdarg.h> 20 #include <stddef.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <unistd.h> 24 25 #define UNIT_TESTING 26 #include <cmocka.h> 27 28 #include <isc/buffer.h> 29 #include <isc/list.h> 30 #include <isc/region.h> 31 #include <isc/result.h> 32 #include <isc/stdtime.h> 33 #include <isc/types.h> 34 #include <isc/util.h> 35 36 #include <dns/db.h> 37 #include <dns/diff.h> 38 #include <dns/dnssec.h> 39 #include <dns/fixedname.h> 40 #include <dns/name.h> 41 #include <dns/rdata.h> 42 #include <dns/rdatastruct.h> 43 #include <dns/rdatatype.h> 44 #include <dns/types.h> 45 #include <dns/zone.h> 46 47 #include <dst/dst.h> 48 49 #include "zone_p.h" 50 51 #include <tests/dns.h> 52 53 /*% 54 * Structure characterizing a single diff tuple in the dns_diff_t structure 55 * prepared by dns__zone_updatesigs(). 56 */ 57 typedef struct { 58 dns_diffop_t op; 59 const char *owner; 60 dns_ttl_t ttl; 61 const char *type; 62 } zonediff_t; 63 64 #define ZONEDIFF_SENTINEL { 0, NULL, 0, NULL } 65 66 /*% 67 * Structure defining a dns__zone_updatesigs() test. 68 */ 69 typedef struct { 70 const char *description; /* test description */ 71 const zonechange_t *changes; /* array of "raw" zone changes */ 72 const zonediff_t *zonediff; /* array of "processed" zone changes 73 * */ 74 } updatesigs_test_params_t; 75 76 static int 77 setup_test(void **state) { 78 isc_result_t result; 79 80 UNUSED(state); 81 82 result = dst_lib_init(mctx, NULL); 83 84 if (result != ISC_R_SUCCESS) { 85 return 1; 86 } 87 88 return 0; 89 } 90 91 static int 92 teardown_test(void **state) { 93 UNUSED(state); 94 95 dst_lib_destroy(); 96 97 return 0; 98 } 99 100 /*% 101 * Check whether the 'found' tuple matches the 'expected' tuple. 'found' is 102 * the 'index'th tuple output by dns__zone_updatesigs() in test 'test'. 103 */ 104 static void 105 compare_tuples(const zonediff_t *expected, dns_difftuple_t *found, 106 size_t index) { 107 char found_covers[DNS_RDATATYPE_FORMATSIZE] = {}; 108 char found_type[DNS_RDATATYPE_FORMATSIZE] = {}; 109 char found_name[DNS_NAME_FORMATSIZE]; 110 isc_consttextregion_t typeregion; 111 dns_fixedname_t expected_fname; 112 dns_rdatatype_t expected_type; 113 dns_name_t *expected_name; 114 dns_rdata_rrsig_t rrsig; 115 isc_buffer_t typebuf; 116 isc_result_t result; 117 118 REQUIRE(expected != NULL); 119 REQUIRE(found != NULL); 120 REQUIRE(index > 0); 121 122 /* 123 * Check operation. 124 */ 125 assert_int_equal(expected->op, found->op); 126 127 /* 128 * Check owner name. 129 */ 130 expected_name = dns_fixedname_initname(&expected_fname); 131 result = dns_name_fromstring(expected_name, expected->owner, 132 dns_rootname, 0, mctx); 133 assert_int_equal(result, ISC_R_SUCCESS); 134 dns_name_format(&found->name, found_name, sizeof(found_name)); 135 assert_true(dns_name_equal(expected_name, &found->name)); 136 137 /* 138 * Check TTL. 139 */ 140 assert_int_equal(expected->ttl, found->ttl); 141 142 /* 143 * Parse expected RR type. 144 */ 145 typeregion.base = expected->type; 146 typeregion.length = strlen(expected->type); 147 result = dns_rdatatype_fromtext(&expected_type, 148 (isc_textregion_t *)&typeregion); 149 assert_int_equal(result, ISC_R_SUCCESS); 150 151 /* 152 * Format found RR type for reporting purposes. 153 */ 154 isc_buffer_init(&typebuf, found_type, sizeof(found_type)); 155 result = dns_rdatatype_totext(found->rdata.type, &typebuf); 156 assert_int_equal(result, ISC_R_SUCCESS); 157 158 /* 159 * Check RR type. 160 */ 161 switch (expected->op) { 162 case DNS_DIFFOP_ADDRESIGN: 163 case DNS_DIFFOP_DELRESIGN: 164 /* 165 * Found tuple must be of type RRSIG. 166 */ 167 assert_int_equal(found->rdata.type, dns_rdatatype_rrsig); 168 if (found->rdata.type != dns_rdatatype_rrsig) { 169 break; 170 } 171 /* 172 * The signature must cover an RRset of type 'expected->type'. 173 */ 174 result = dns_rdata_tostruct(&found->rdata, &rrsig, NULL); 175 assert_int_equal(result, ISC_R_SUCCESS); 176 isc_buffer_init(&typebuf, found_covers, sizeof(found_covers)); 177 result = dns_rdatatype_totext(rrsig.covered, &typebuf); 178 assert_int_equal(result, ISC_R_SUCCESS); 179 assert_int_equal(expected_type, rrsig.covered); 180 break; 181 default: 182 /* 183 * Found tuple must be of type 'expected->type'. 184 */ 185 assert_int_equal(expected_type, found->rdata.type); 186 break; 187 } 188 } 189 190 /*% 191 * Perform a single dns__zone_updatesigs() test defined in 'test'. All other 192 * arguments are expected to remain constant between subsequent invocations of 193 * this function. 194 */ 195 static void 196 updatesigs_test(const updatesigs_test_params_t *test, dns_zone_t *zone, 197 dns_db_t *db, dst_key_t *zone_keys[], unsigned int nkeys, 198 isc_stdtime_t now) { 199 size_t tuples_expected, tuples_found, index; 200 dns_dbversion_t *version = NULL; 201 dns_diff_t raw_diff, zone_diff; 202 const zonediff_t *expected; 203 dns_difftuple_t *found; 204 isc_result_t result; 205 206 dns__zonediff_t zonediff = { 207 .diff = &zone_diff, 208 .offline = false, 209 }; 210 211 REQUIRE(test != NULL); 212 REQUIRE(test->description != NULL); 213 REQUIRE(test->changes != NULL); 214 REQUIRE(zone != NULL); 215 REQUIRE(db != NULL); 216 REQUIRE(zone_keys != NULL); 217 218 /* 219 * Create a new version of the zone's database. 220 */ 221 result = dns_db_newversion(db, &version); 222 assert_int_equal(result, ISC_R_SUCCESS); 223 224 /* 225 * Create a diff representing the supplied changes. 226 */ 227 result = dns_test_difffromchanges(&raw_diff, test->changes, false); 228 assert_int_equal(result, ISC_R_SUCCESS); 229 230 /* 231 * Apply the "raw" diff to the new version of the zone's database as 232 * this is what dns__zone_updatesigs() expects to happen before it is 233 * called. 234 */ 235 dns_diff_apply(&raw_diff, db, version); 236 237 /* 238 * Initialize the structure dns__zone_updatesigs() will modify. 239 */ 240 dns_diff_init(mctx, &zone_diff); 241 242 /* 243 * Check whether dns__zone_updatesigs() behaves as expected. 244 */ 245 result = dns__zone_updatesigs(&raw_diff, db, version, zone_keys, nkeys, 246 zone, now - 3600, now + 3600, 0, now, 247 &zonediff); 248 assert_int_equal(result, ISC_R_SUCCESS); 249 assert_true(ISC_LIST_EMPTY(raw_diff.tuples)); 250 assert_false(ISC_LIST_EMPTY(zone_diff.tuples)); 251 252 /* 253 * Ensure that the number of tuples in the zone diff is as expected. 254 */ 255 256 tuples_expected = 0; 257 for (expected = test->zonediff; expected->owner != NULL; expected++) { 258 tuples_expected++; 259 } 260 261 tuples_found = 0; 262 for (found = ISC_LIST_HEAD(zone_diff.tuples); found != NULL; 263 found = ISC_LIST_NEXT(found, link)) 264 { 265 tuples_found++; 266 } 267 268 assert_int_equal(tuples_expected, tuples_found); 269 270 /* 271 * Ensure that every tuple in the zone diff matches expectations. 272 */ 273 expected = test->zonediff; 274 index = 1; 275 for (found = ISC_LIST_HEAD(zone_diff.tuples); found != NULL; 276 found = ISC_LIST_NEXT(found, link)) 277 { 278 compare_tuples(expected, found, index); 279 expected++; 280 index++; 281 } 282 283 /* 284 * Apply changes to zone database contents and clean up. 285 */ 286 dns_db_closeversion(db, &version, true); 287 dns_diff_clear(&zone_diff); 288 dns_diff_clear(&raw_diff); 289 } 290 291 /* dns__zone_updatesigs() tests */ 292 ISC_RUN_TEST_IMPL(updatesigs_next) { 293 dst_key_t *zone_keys[DNS_MAXZONEKEYS]; 294 dns_zone_t *zone = NULL; 295 dns_db_t *db = NULL; 296 isc_result_t result; 297 unsigned int nkeys; 298 isc_stdtime_t now = isc_stdtime_now(); 299 size_t i; 300 301 UNUSED(state); 302 303 /* 304 * Prepare a zone along with its signing keys. 305 */ 306 307 result = dns_test_makezone("example", &zone, NULL, false); 308 assert_int_equal(result, ISC_R_SUCCESS); 309 310 result = dns_test_loaddb(&db, dns_dbtype_zone, "example", 311 "testdata/master/master18.data"); 312 assert_int_equal(result, DNS_R_SEENINCLUDE); 313 314 result = dns_zone_setkeydirectory(zone, TESTS_DIR "/testkeys"); 315 assert_int_equal(result, ISC_R_SUCCESS); 316 317 result = dns_zone_findkeys(zone, db, NULL, now, mctx, DNS_MAXZONEKEYS, 318 zone_keys, &nkeys); 319 assert_int_equal(result, ISC_R_SUCCESS); 320 assert_int_equal(nkeys, 2); 321 322 /* 323 * Define the tests to be run. Note that changes to zone database 324 * contents introduced by each test are preserved between tests. 325 */ 326 327 const zonechange_t changes_add[] = { 328 { DNS_DIFFOP_ADD, "foo.example", 300, "TXT", "foo" }, 329 { DNS_DIFFOP_ADD, "bar.example", 600, "TXT", "bar" }, 330 ZONECHANGE_SENTINEL, 331 }; 332 const zonediff_t zonediff_add[] = { 333 { DNS_DIFFOP_ADDRESIGN, "foo.example", 300, "TXT" }, 334 { DNS_DIFFOP_ADD, "foo.example", 300, "TXT" }, 335 { DNS_DIFFOP_ADDRESIGN, "bar.example", 600, "TXT" }, 336 { DNS_DIFFOP_ADD, "bar.example", 600, "TXT" }, 337 ZONEDIFF_SENTINEL, 338 }; 339 const updatesigs_test_params_t test_add = { 340 .description = "add new RRsets", 341 .changes = changes_add, 342 .zonediff = zonediff_add, 343 }; 344 345 const zonechange_t changes_append[] = { 346 { DNS_DIFFOP_ADD, "foo.example", 300, "TXT", "foo1" }, 347 { DNS_DIFFOP_ADD, "foo.example", 300, "TXT", "foo2" }, 348 ZONECHANGE_SENTINEL, 349 }; 350 const zonediff_t zonediff_append[] = { 351 { DNS_DIFFOP_DELRESIGN, "foo.example", 300, "TXT" }, 352 { DNS_DIFFOP_ADDRESIGN, "foo.example", 300, "TXT" }, 353 { DNS_DIFFOP_ADD, "foo.example", 300, "TXT" }, 354 { DNS_DIFFOP_ADD, "foo.example", 300, "TXT" }, 355 ZONEDIFF_SENTINEL, 356 }; 357 const updatesigs_test_params_t test_append = { 358 .description = "append multiple RRs to an existing RRset", 359 .changes = changes_append, 360 .zonediff = zonediff_append, 361 }; 362 363 const zonechange_t changes_replace[] = { 364 { DNS_DIFFOP_DEL, "bar.example", 600, "TXT", "bar" }, 365 { DNS_DIFFOP_ADD, "bar.example", 600, "TXT", "rab" }, 366 ZONECHANGE_SENTINEL, 367 }; 368 const zonediff_t zonediff_replace[] = { 369 { DNS_DIFFOP_DELRESIGN, "bar.example", 600, "TXT" }, 370 { DNS_DIFFOP_ADDRESIGN, "bar.example", 600, "TXT" }, 371 { DNS_DIFFOP_DEL, "bar.example", 600, "TXT" }, 372 { DNS_DIFFOP_ADD, "bar.example", 600, "TXT" }, 373 ZONEDIFF_SENTINEL, 374 }; 375 const updatesigs_test_params_t test_replace = { 376 .description = "replace an existing RRset", 377 .changes = changes_replace, 378 .zonediff = zonediff_replace, 379 }; 380 381 const zonechange_t changes_delete[] = { 382 { DNS_DIFFOP_DEL, "bar.example", 600, "TXT", "rab" }, 383 ZONECHANGE_SENTINEL, 384 }; 385 const zonediff_t zonediff_delete[] = { 386 { DNS_DIFFOP_DELRESIGN, "bar.example", 600, "TXT" }, 387 { DNS_DIFFOP_DEL, "bar.example", 600, "TXT" }, 388 ZONEDIFF_SENTINEL, 389 }; 390 const updatesigs_test_params_t test_delete = { 391 .description = "delete an existing RRset", 392 .changes = changes_delete, 393 .zonediff = zonediff_delete, 394 }; 395 396 const zonechange_t changes_mixed[] = { 397 { DNS_DIFFOP_ADD, "baz.example", 900, "TXT", "baz1" }, 398 { DNS_DIFFOP_ADD, "baz.example", 900, "A", "127.0.0.1" }, 399 { DNS_DIFFOP_ADD, "baz.example", 900, "TXT", "baz2" }, 400 { DNS_DIFFOP_ADD, "baz.example", 900, "AAAA", "::1" }, 401 ZONECHANGE_SENTINEL, 402 }; 403 const zonediff_t zonediff_mixed[] = { 404 { DNS_DIFFOP_ADDRESIGN, "baz.example", 900, "TXT" }, 405 { DNS_DIFFOP_ADD, "baz.example", 900, "TXT" }, 406 { DNS_DIFFOP_ADD, "baz.example", 900, "TXT" }, 407 { DNS_DIFFOP_ADDRESIGN, "baz.example", 900, "A" }, 408 { DNS_DIFFOP_ADD, "baz.example", 900, "A" }, 409 { DNS_DIFFOP_ADDRESIGN, "baz.example", 900, "AAAA" }, 410 { DNS_DIFFOP_ADD, "baz.example", 900, "AAAA" }, 411 ZONEDIFF_SENTINEL, 412 }; 413 const updatesigs_test_params_t test_mixed = { 414 .description = "add different RRsets with common owner name", 415 .changes = changes_mixed, 416 .zonediff = zonediff_mixed, 417 }; 418 419 const updatesigs_test_params_t *tests[] = { 420 &test_add, &test_append, &test_replace, 421 &test_delete, &test_mixed, 422 }; 423 424 /* 425 * Run tests. 426 */ 427 for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { 428 updatesigs_test(tests[i], zone, db, zone_keys, nkeys, now); 429 } 430 431 /* 432 * Clean up. 433 */ 434 for (i = 0; i < nkeys; i++) { 435 dst_key_free(&zone_keys[i]); 436 } 437 dns_db_detach(&db); 438 dns_zone_detach(&zone); 439 } 440 441 ISC_TEST_LIST_START 442 ISC_TEST_ENTRY_CUSTOM(updatesigs_next, setup_test, teardown_test) 443 ISC_TEST_LIST_END 444 445 ISC_TEST_MAIN 446