1*bcda20f6Schristos /* $NetBSD: private.c,v 1.10 2025/01/26 16:25:24 christos Exp $ */ 2d68c78b8Schristos 3d68c78b8Schristos /* 4d68c78b8Schristos * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5d68c78b8Schristos * 68596601aSchristos * SPDX-License-Identifier: MPL-2.0 78596601aSchristos * 8d68c78b8Schristos * This Source Code Form is subject to the terms of the Mozilla Public 9d68c78b8Schristos * License, v. 2.0. If a copy of the MPL was not distributed with this 10fce770bdSchristos * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11d68c78b8Schristos * 12d68c78b8Schristos * See the COPYRIGHT file distributed with this work for additional 13d68c78b8Schristos * information regarding copyright ownership. 14d68c78b8Schristos */ 15d68c78b8Schristos 16d4a20c3eSchristos #include <stdbool.h> 17d68c78b8Schristos 18d68c78b8Schristos #include <isc/base64.h> 19d68c78b8Schristos #include <isc/result.h> 20d68c78b8Schristos #include <isc/string.h> 21d68c78b8Schristos #include <isc/types.h> 22d68c78b8Schristos #include <isc/util.h> 23d68c78b8Schristos 24d68c78b8Schristos #include <dns/nsec3.h> 25d68c78b8Schristos #include <dns/private.h> 26d68c78b8Schristos 27d68c78b8Schristos /* 28d68c78b8Schristos * We need to build the relevant chain if there exists a NSEC/NSEC3PARAM 29d68c78b8Schristos * at the apex; normally only one or the other of NSEC/NSEC3PARAM will exist. 30d68c78b8Schristos * 31d68c78b8Schristos * If a NSEC3PARAM RRset exists then we will need to build a NSEC chain 32d68c78b8Schristos * if all the NSEC3PARAM records (and associated chains) are slated for 33d68c78b8Schristos * destruction and we have not been told to NOT build the NSEC chain. 34d68c78b8Schristos * 35d68c78b8Schristos * If the NSEC set exist then check to see if there is a request to create 36d68c78b8Schristos * a NSEC3 chain. 37d68c78b8Schristos * 38d68c78b8Schristos * If neither NSEC/NSEC3PARAM RRsets exist at the origin and the private 39d68c78b8Schristos * type exists then we need to examine it to determine if NSEC3 chain has 40d68c78b8Schristos * been requested to be built otherwise a NSEC chain needs to be built. 41d68c78b8Schristos */ 42d68c78b8Schristos 43d68c78b8Schristos #define REMOVE(x) (((x) & DNS_NSEC3FLAG_REMOVE) != 0) 44d68c78b8Schristos #define CREATE(x) (((x) & DNS_NSEC3FLAG_CREATE) != 0) 45d68c78b8Schristos #define INITIAL(x) (((x) & DNS_NSEC3FLAG_INITIAL) != 0) 46d68c78b8Schristos #define NONSEC(x) (((x) & DNS_NSEC3FLAG_NONSEC) != 0) 47d68c78b8Schristos 485606745fSchristos #define CHECK(x) \ 495606745fSchristos do { \ 50d68c78b8Schristos result = (x); \ 51d68c78b8Schristos if (result != ISC_R_SUCCESS) \ 52d68c78b8Schristos goto failure; \ 5353cc4e50Srillig } while (0) 54d68c78b8Schristos 55d68c78b8Schristos /* 56d68c78b8Schristos * Work out if 'param' should be ignored or not (i.e. it is in the process 57d68c78b8Schristos * of being removed). 58d68c78b8Schristos * 59d68c78b8Schristos * Note: we 'belt-and-braces' here by also checking for a CREATE private 60d68c78b8Schristos * record and keep the param record in this case. 61d68c78b8Schristos */ 62d68c78b8Schristos 63d4a20c3eSchristos static bool 64d68c78b8Schristos ignore(dns_rdata_t *param, dns_rdataset_t *privateset) { 65d68c78b8Schristos isc_result_t result; 66d68c78b8Schristos 675606745fSchristos for (result = dns_rdataset_first(privateset); result == ISC_R_SUCCESS; 685606745fSchristos result = dns_rdataset_next(privateset)) 695606745fSchristos { 70d68c78b8Schristos unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; 71d68c78b8Schristos dns_rdata_t private = DNS_RDATA_INIT; 72d68c78b8Schristos dns_rdata_t rdata = DNS_RDATA_INIT; 73d68c78b8Schristos 74d68c78b8Schristos dns_rdataset_current(privateset, &private); 755606745fSchristos if (!dns_nsec3param_fromprivate(&private, &rdata, buf, 76903adeddSchristos sizeof(buf))) 77903adeddSchristos { 78d68c78b8Schristos continue; 795606745fSchristos } 80d68c78b8Schristos /* 81d68c78b8Schristos * We are going to create a new NSEC3 chain so it 82d68c78b8Schristos * doesn't matter if we are removing this one. 83d68c78b8Schristos */ 845606745fSchristos if (CREATE(rdata.data[1])) { 85*bcda20f6Schristos return false; 865606745fSchristos } 87d68c78b8Schristos if (rdata.data[0] != param->data[0] || 88d68c78b8Schristos rdata.data[2] != param->data[2] || 89d68c78b8Schristos rdata.data[3] != param->data[3] || 90d68c78b8Schristos rdata.data[4] != param->data[4] || 91d68c78b8Schristos memcmp(&rdata.data[5], ¶m->data[5], param->data[4])) 925606745fSchristos { 93d68c78b8Schristos continue; 945606745fSchristos } 95d68c78b8Schristos /* 96d68c78b8Schristos * The removal of this NSEC3 chain does NOT cause a 97d68c78b8Schristos * NSEC chain to be created so we don't need to tell 98d68c78b8Schristos * the caller that it will be removed. 99d68c78b8Schristos */ 1005606745fSchristos if (NONSEC(rdata.data[1])) { 101*bcda20f6Schristos return false; 1025606745fSchristos } 103*bcda20f6Schristos return true; 104d68c78b8Schristos } 105*bcda20f6Schristos return false; 106d68c78b8Schristos } 107d68c78b8Schristos 108d68c78b8Schristos isc_result_t 109d68c78b8Schristos dns_private_chains(dns_db_t *db, dns_dbversion_t *ver, 1105606745fSchristos dns_rdatatype_t privatetype, bool *build_nsec, 1115606745fSchristos bool *build_nsec3) { 112d68c78b8Schristos dns_dbnode_t *node; 113d68c78b8Schristos dns_rdataset_t nsecset, nsec3paramset, privateset; 114d4a20c3eSchristos bool nsec3chain; 115d4a20c3eSchristos bool signing; 116d68c78b8Schristos isc_result_t result; 117d68c78b8Schristos unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; 118d68c78b8Schristos unsigned int count; 119d68c78b8Schristos 120d68c78b8Schristos node = NULL; 121d68c78b8Schristos dns_rdataset_init(&nsecset); 122d68c78b8Schristos dns_rdataset_init(&nsec3paramset); 123d68c78b8Schristos dns_rdataset_init(&privateset); 124d68c78b8Schristos 125d68c78b8Schristos CHECK(dns_db_getoriginnode(db, &node)); 126d68c78b8Schristos 1275606745fSchristos result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec, 0, 1285606745fSchristos (isc_stdtime_t)0, &nsecset, NULL); 129d68c78b8Schristos 1305606745fSchristos if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { 131d68c78b8Schristos goto failure; 1325606745fSchristos } 133d68c78b8Schristos 1345606745fSchristos result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0, 1355606745fSchristos (isc_stdtime_t)0, &nsec3paramset, NULL); 1365606745fSchristos if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { 137d68c78b8Schristos goto failure; 1385606745fSchristos } 139d68c78b8Schristos 140d68c78b8Schristos if (dns_rdataset_isassociated(&nsecset) && 1415606745fSchristos dns_rdataset_isassociated(&nsec3paramset)) 1425606745fSchristos { 1435606745fSchristos if (build_nsec != NULL) { 144d4a20c3eSchristos *build_nsec = true; 1455606745fSchristos } 1465606745fSchristos if (build_nsec3 != NULL) { 147d4a20c3eSchristos *build_nsec3 = true; 1485606745fSchristos } 149d68c78b8Schristos goto success; 150d68c78b8Schristos } 151d68c78b8Schristos 152d68c78b8Schristos if (privatetype != (dns_rdatatype_t)0) { 1535606745fSchristos result = dns_db_findrdataset(db, node, ver, privatetype, 0, 1545606745fSchristos (isc_stdtime_t)0, &privateset, 1555606745fSchristos NULL); 1565606745fSchristos if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { 157d68c78b8Schristos goto failure; 158d68c78b8Schristos } 1595606745fSchristos } 160d68c78b8Schristos 161d68c78b8Schristos /* 162d68c78b8Schristos * Look to see if we also need to be creating a NSEC3 chain. 163d68c78b8Schristos */ 164d68c78b8Schristos if (dns_rdataset_isassociated(&nsecset)) { 1655606745fSchristos if (build_nsec != NULL) { 166d4a20c3eSchristos *build_nsec = true; 1675606745fSchristos } 1685606745fSchristos if (build_nsec3 != NULL) { 169d4a20c3eSchristos *build_nsec3 = false; 1705606745fSchristos } 1715606745fSchristos if (!dns_rdataset_isassociated(&privateset)) { 172d68c78b8Schristos goto success; 1735606745fSchristos } 174d68c78b8Schristos for (result = dns_rdataset_first(&privateset); 175d68c78b8Schristos result == ISC_R_SUCCESS; 1765606745fSchristos result = dns_rdataset_next(&privateset)) 1775606745fSchristos { 178d68c78b8Schristos dns_rdata_t private = DNS_RDATA_INIT; 179d68c78b8Schristos dns_rdata_t rdata = DNS_RDATA_INIT; 180d68c78b8Schristos 181d68c78b8Schristos dns_rdataset_current(&privateset, &private); 1825606745fSchristos if (!dns_nsec3param_fromprivate(&private, &rdata, buf, 183903adeddSchristos sizeof(buf))) 184903adeddSchristos { 185d68c78b8Schristos continue; 1865606745fSchristos } 1875606745fSchristos if (REMOVE(rdata.data[1])) { 188d68c78b8Schristos continue; 1895606745fSchristos } 1905606745fSchristos if (build_nsec3 != NULL) { 191d4a20c3eSchristos *build_nsec3 = true; 1925606745fSchristos } 193d68c78b8Schristos break; 194d68c78b8Schristos } 195d68c78b8Schristos goto success; 196d68c78b8Schristos } 197d68c78b8Schristos 198d68c78b8Schristos if (dns_rdataset_isassociated(&nsec3paramset)) { 1995606745fSchristos if (build_nsec3 != NULL) { 200d4a20c3eSchristos *build_nsec3 = true; 2015606745fSchristos } 2025606745fSchristos if (build_nsec != NULL) { 203d4a20c3eSchristos *build_nsec = false; 2045606745fSchristos } 2055606745fSchristos if (!dns_rdataset_isassociated(&privateset)) { 206d68c78b8Schristos goto success; 2075606745fSchristos } 208d68c78b8Schristos /* 209d68c78b8Schristos * If we are in the process of building a new NSEC3 chain 210d68c78b8Schristos * then we don't need to build a NSEC chain. 211d68c78b8Schristos */ 212d68c78b8Schristos for (result = dns_rdataset_first(&privateset); 213d68c78b8Schristos result == ISC_R_SUCCESS; 2145606745fSchristos result = dns_rdataset_next(&privateset)) 2155606745fSchristos { 216d68c78b8Schristos dns_rdata_t private = DNS_RDATA_INIT; 217d68c78b8Schristos dns_rdata_t rdata = DNS_RDATA_INIT; 218d68c78b8Schristos 219d68c78b8Schristos dns_rdataset_current(&privateset, &private); 2205606745fSchristos if (!dns_nsec3param_fromprivate(&private, &rdata, buf, 221903adeddSchristos sizeof(buf))) 222903adeddSchristos { 223d68c78b8Schristos continue; 2245606745fSchristos } 2255606745fSchristos if (CREATE(rdata.data[1])) { 226d68c78b8Schristos goto success; 227d68c78b8Schristos } 2285606745fSchristos } 229d68c78b8Schristos 230d68c78b8Schristos /* 231d68c78b8Schristos * Check to see if there will be a active NSEC3CHAIN once 232d68c78b8Schristos * the changes queued complete. 233d68c78b8Schristos */ 234d68c78b8Schristos count = 0; 235d68c78b8Schristos for (result = dns_rdataset_first(&nsec3paramset); 236d68c78b8Schristos result == ISC_R_SUCCESS; 2375606745fSchristos result = dns_rdataset_next(&nsec3paramset)) 2385606745fSchristos { 239d68c78b8Schristos dns_rdata_t rdata = DNS_RDATA_INIT; 240d68c78b8Schristos 241d68c78b8Schristos /* 242d68c78b8Schristos * If there is more that one NSEC3 chain present then 243d68c78b8Schristos * we don't need to construct a NSEC chain. 244d68c78b8Schristos */ 2455606745fSchristos if (++count > 1) { 246d68c78b8Schristos goto success; 2475606745fSchristos } 248d68c78b8Schristos dns_rdataset_current(&nsec3paramset, &rdata); 2495606745fSchristos if (ignore(&rdata, &privateset)) { 250d68c78b8Schristos continue; 2515606745fSchristos } 252d68c78b8Schristos /* 253d68c78b8Schristos * We still have a good NSEC3 chain or we are 254d68c78b8Schristos * not creating a NSEC chain as NONSEC is set. 255d68c78b8Schristos */ 256d68c78b8Schristos goto success; 257d68c78b8Schristos } 258d68c78b8Schristos 259d68c78b8Schristos /* 260d68c78b8Schristos * The last NSEC3 chain is being removed and does not have 261d68c78b8Schristos * have NONSEC set. 262d68c78b8Schristos */ 2635606745fSchristos if (build_nsec != NULL) { 264d4a20c3eSchristos *build_nsec = true; 2655606745fSchristos } 266d68c78b8Schristos goto success; 267d68c78b8Schristos } 268d68c78b8Schristos 2695606745fSchristos if (build_nsec != NULL) { 270d4a20c3eSchristos *build_nsec = false; 2715606745fSchristos } 2725606745fSchristos if (build_nsec3 != NULL) { 273d4a20c3eSchristos *build_nsec3 = false; 2745606745fSchristos } 2755606745fSchristos if (!dns_rdataset_isassociated(&privateset)) { 276d68c78b8Schristos goto success; 2775606745fSchristos } 278d68c78b8Schristos 279d4a20c3eSchristos signing = false; 280d4a20c3eSchristos nsec3chain = false; 281d68c78b8Schristos 2825606745fSchristos for (result = dns_rdataset_first(&privateset); result == ISC_R_SUCCESS; 2835606745fSchristos result = dns_rdataset_next(&privateset)) 2845606745fSchristos { 285d68c78b8Schristos dns_rdata_t rdata = DNS_RDATA_INIT; 286d68c78b8Schristos dns_rdata_t private = DNS_RDATA_INIT; 287d68c78b8Schristos 288d68c78b8Schristos dns_rdataset_current(&privateset, &private); 2895606745fSchristos if (!dns_nsec3param_fromprivate(&private, &rdata, buf, 290903adeddSchristos sizeof(buf))) 291903adeddSchristos { 292d68c78b8Schristos /* 293d68c78b8Schristos * Look for record that says we are signing the 294d68c78b8Schristos * zone with a key. 295d68c78b8Schristos */ 296d68c78b8Schristos if (private.length == 5 && private.data[0] != 0 && 297d68c78b8Schristos private.data[3] == 0 && private.data[4] == 0) 2985606745fSchristos { 299d4a20c3eSchristos signing = true; 3005606745fSchristos } 301d68c78b8Schristos } else { 3025606745fSchristos if (CREATE(rdata.data[1])) { 303d4a20c3eSchristos nsec3chain = true; 304d68c78b8Schristos } 305d68c78b8Schristos } 3065606745fSchristos } 307d68c78b8Schristos 308d68c78b8Schristos if (signing) { 309d68c78b8Schristos if (nsec3chain) { 3105606745fSchristos if (build_nsec3 != NULL) { 311d4a20c3eSchristos *build_nsec3 = true; 3125606745fSchristos } 313d68c78b8Schristos } else { 3145606745fSchristos if (build_nsec != NULL) { 315d4a20c3eSchristos *build_nsec = true; 316d68c78b8Schristos } 317d68c78b8Schristos } 3185606745fSchristos } 319d68c78b8Schristos 320d68c78b8Schristos success: 321d68c78b8Schristos result = ISC_R_SUCCESS; 322d68c78b8Schristos failure: 3235606745fSchristos if (dns_rdataset_isassociated(&nsecset)) { 324d68c78b8Schristos dns_rdataset_disassociate(&nsecset); 3255606745fSchristos } 3265606745fSchristos if (dns_rdataset_isassociated(&nsec3paramset)) { 327d68c78b8Schristos dns_rdataset_disassociate(&nsec3paramset); 3285606745fSchristos } 3295606745fSchristos if (dns_rdataset_isassociated(&privateset)) { 330d68c78b8Schristos dns_rdataset_disassociate(&privateset); 3315606745fSchristos } 3325606745fSchristos if (node != NULL) { 333d68c78b8Schristos dns_db_detachnode(db, &node); 3345606745fSchristos } 335*bcda20f6Schristos return result; 336d68c78b8Schristos } 337d68c78b8Schristos 338d68c78b8Schristos isc_result_t 339d68c78b8Schristos dns_private_totext(dns_rdata_t *private, isc_buffer_t *buf) { 340d68c78b8Schristos isc_result_t result; 341d68c78b8Schristos 3425606745fSchristos if (private->length < 5) { 343*bcda20f6Schristos return ISC_R_NOTFOUND; 3445606745fSchristos } 345d68c78b8Schristos 346d68c78b8Schristos if (private->data[0] == 0) { 347d68c78b8Schristos unsigned char nsec3buf[DNS_NSEC3PARAM_BUFFERSIZE]; 348d68c78b8Schristos unsigned char newbuf[DNS_NSEC3PARAM_BUFFERSIZE]; 349d68c78b8Schristos dns_rdata_t rdata = DNS_RDATA_INIT; 350d68c78b8Schristos dns_rdata_nsec3param_t nsec3param; 351d4a20c3eSchristos bool del, init, nonsec; 352d68c78b8Schristos isc_buffer_t b; 353d68c78b8Schristos 354d68c78b8Schristos if (!dns_nsec3param_fromprivate(private, &rdata, nsec3buf, 355903adeddSchristos sizeof(nsec3buf))) 356903adeddSchristos { 357d68c78b8Schristos CHECK(ISC_R_FAILURE); 3585606745fSchristos } 359d68c78b8Schristos 360d68c78b8Schristos CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); 361d68c78b8Schristos 362d4a20c3eSchristos del = ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0); 363d4a20c3eSchristos init = ((nsec3param.flags & DNS_NSEC3FLAG_INITIAL) != 0); 364d4a20c3eSchristos nonsec = ((nsec3param.flags & DNS_NSEC3FLAG_NONSEC) != 0); 365d68c78b8Schristos 3665606745fSchristos nsec3param.flags &= 3675606745fSchristos ~(DNS_NSEC3FLAG_CREATE | DNS_NSEC3FLAG_REMOVE | 3685606745fSchristos DNS_NSEC3FLAG_INITIAL | DNS_NSEC3FLAG_NONSEC); 369d68c78b8Schristos 3705606745fSchristos if (init) { 371d68c78b8Schristos isc_buffer_putstr(buf, "Pending NSEC3 chain "); 3725606745fSchristos } else if (del) { 373d68c78b8Schristos isc_buffer_putstr(buf, "Removing NSEC3 chain "); 3745606745fSchristos } else { 375d68c78b8Schristos isc_buffer_putstr(buf, "Creating NSEC3 chain "); 3765606745fSchristos } 377d68c78b8Schristos 378d68c78b8Schristos dns_rdata_reset(&rdata); 379d68c78b8Schristos isc_buffer_init(&b, newbuf, sizeof(newbuf)); 380d68c78b8Schristos CHECK(dns_rdata_fromstruct(&rdata, dns_rdataclass_in, 381d68c78b8Schristos dns_rdatatype_nsec3param, 382d68c78b8Schristos &nsec3param, &b)); 383d68c78b8Schristos 384d68c78b8Schristos CHECK(dns_rdata_totext(&rdata, NULL, buf)); 385d68c78b8Schristos 3865606745fSchristos if (del && !nonsec) { 387d68c78b8Schristos isc_buffer_putstr(buf, " / creating NSEC chain"); 3885606745fSchristos } 389d68c78b8Schristos } else if (private->length == 5) { 390d68c78b8Schristos unsigned char alg = private->data[0]; 391d68c78b8Schristos dns_keytag_t keyid = (private->data[2] | private->data[1] << 8); 3928596601aSchristos char keybuf[DNS_SECALG_FORMATSIZE + BUFSIZ], 3938596601aSchristos algbuf[DNS_SECALG_FORMATSIZE]; 394d4a20c3eSchristos bool del = private->data[3]; 395d4a20c3eSchristos bool complete = private->data[4]; 396d68c78b8Schristos 3975606745fSchristos if (del && complete) { 398d68c78b8Schristos isc_buffer_putstr(buf, "Done removing signatures for "); 3995606745fSchristos } else if (del) { 400d68c78b8Schristos isc_buffer_putstr(buf, "Removing signatures for "); 4015606745fSchristos } else if (complete) { 402d68c78b8Schristos isc_buffer_putstr(buf, "Done signing with "); 4035606745fSchristos } else { 404d68c78b8Schristos isc_buffer_putstr(buf, "Signing with "); 4055606745fSchristos } 406d68c78b8Schristos 407d68c78b8Schristos dns_secalg_format(alg, algbuf, sizeof(algbuf)); 408d68c78b8Schristos snprintf(keybuf, sizeof(keybuf), "key %d/%s", keyid, algbuf); 409d68c78b8Schristos isc_buffer_putstr(buf, keybuf); 4105606745fSchristos } else { 411*bcda20f6Schristos return ISC_R_NOTFOUND; 4125606745fSchristos } 413d68c78b8Schristos 414d68c78b8Schristos isc_buffer_putuint8(buf, 0); 415d68c78b8Schristos result = ISC_R_SUCCESS; 416d68c78b8Schristos failure: 417*bcda20f6Schristos return result; 418d68c78b8Schristos } 419