1/* $NetBSD: kaspconf.c,v 1.7 2024/02/21 22:52:44 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 <stdbool.h> 18#include <stdlib.h> 19 20#include <isc/mem.h> 21#include <isc/print.h> 22#include <isc/region.h> 23#include <isc/result.h> 24#include <isc/string.h> 25#include <isc/types.h> 26#include <isc/util.h> 27 28#include <dns/kasp.h> 29#include <dns/keyvalues.h> 30#include <dns/log.h> 31#include <dns/nsec3.h> 32#include <dns/secalg.h> 33#include <dns/ttl.h> 34 35#include <isccfg/cfg.h> 36#include <isccfg/duration.h> 37#include <isccfg/kaspconf.h> 38#include <isccfg/namedconf.h> 39 40#define DEFAULT_NSEC3PARAM_ITER 0 41#define DEFAULT_NSEC3PARAM_SALTLEN 0 42 43/* 44 * Utility function for getting a configuration option. 45 */ 46static isc_result_t 47confget(cfg_obj_t const *const *maps, const char *name, const cfg_obj_t **obj) { 48 for (size_t i = 0;; i++) { 49 if (maps[i] == NULL) { 50 return (ISC_R_NOTFOUND); 51 } 52 if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS) { 53 return (ISC_R_SUCCESS); 54 } 55 } 56} 57 58/* 59 * Utility function for parsing durations from string. 60 */ 61static uint32_t 62parse_duration(const char *str) { 63 uint32_t time = 0; 64 isccfg_duration_t duration; 65 isc_result_t result; 66 isc_textregion_t tr; 67 68 DE_CONST(str, tr.base); 69 tr.length = strlen(tr.base); 70 result = isccfg_parse_duration(&tr, &duration); 71 if (result == ISC_R_SUCCESS) { 72 time = isccfg_duration_toseconds(&duration); 73 } 74 return (time); 75} 76 77/* 78 * Utility function for configuring durations. 79 */ 80static uint32_t 81get_duration(const cfg_obj_t **maps, const char *option, const char *dfl) { 82 const cfg_obj_t *obj; 83 isc_result_t result; 84 obj = NULL; 85 86 result = confget(maps, option, &obj); 87 if (result == ISC_R_NOTFOUND) { 88 return (parse_duration(dfl)); 89 } 90 INSIST(result == ISC_R_SUCCESS); 91 return (cfg_obj_asduration(obj)); 92} 93 94/* 95 * Create a new kasp key derived from configuration. 96 */ 97static isc_result_t 98cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, 99 isc_log_t *logctx, uint32_t ksk_min_lifetime, 100 uint32_t zsk_min_lifetime) { 101 isc_result_t result; 102 dns_kasp_key_t *key = NULL; 103 104 /* Create a new key reference. */ 105 result = dns_kasp_key_create(kasp, &key); 106 if (result != ISC_R_SUCCESS) { 107 return (result); 108 } 109 110 if (config == NULL) { 111 /* We are creating a key reference for the default kasp. */ 112 key->role |= DNS_KASP_KEY_ROLE_KSK | DNS_KASP_KEY_ROLE_ZSK; 113 key->lifetime = 0; /* unlimited */ 114 key->algorithm = DNS_KEYALG_ECDSA256; 115 key->length = -1; 116 } else { 117 const char *rolestr = NULL; 118 const cfg_obj_t *obj = NULL; 119 isc_consttextregion_t alg; 120 bool error = false; 121 122 rolestr = cfg_obj_asstring(cfg_tuple_get(config, "role")); 123 if (strcmp(rolestr, "ksk") == 0) { 124 key->role |= DNS_KASP_KEY_ROLE_KSK; 125 } else if (strcmp(rolestr, "zsk") == 0) { 126 key->role |= DNS_KASP_KEY_ROLE_ZSK; 127 } else if (strcmp(rolestr, "csk") == 0) { 128 key->role |= DNS_KASP_KEY_ROLE_KSK; 129 key->role |= DNS_KASP_KEY_ROLE_ZSK; 130 } 131 132 key->lifetime = 0; /* unlimited */ 133 obj = cfg_tuple_get(config, "lifetime"); 134 if (cfg_obj_isduration(obj)) { 135 key->lifetime = cfg_obj_asduration(obj); 136 } 137 if (key->lifetime > 0) { 138 if (key->lifetime < 30 * (24 * 3600)) { 139 cfg_obj_log(obj, logctx, ISC_LOG_WARNING, 140 "dnssec-policy: key lifetime is " 141 "shorter than 30 days"); 142 } 143 if ((key->role & DNS_KASP_KEY_ROLE_KSK) != 0 && 144 key->lifetime <= ksk_min_lifetime) 145 { 146 error = true; 147 } 148 if ((key->role & DNS_KASP_KEY_ROLE_ZSK) != 0 && 149 key->lifetime <= zsk_min_lifetime) 150 { 151 error = true; 152 } 153 if (error) { 154 cfg_obj_log(obj, logctx, ISC_LOG_ERROR, 155 "dnssec-policy: key lifetime is " 156 "shorter than the time it takes to " 157 "do a rollover"); 158 result = ISC_R_FAILURE; 159 goto cleanup; 160 } 161 } 162 163 obj = cfg_tuple_get(config, "algorithm"); 164 alg.base = cfg_obj_asstring(obj); 165 alg.length = strlen(alg.base); 166 result = dns_secalg_fromtext(&key->algorithm, 167 (isc_textregion_t *)&alg); 168 if (result != ISC_R_SUCCESS) { 169 cfg_obj_log(obj, logctx, ISC_LOG_ERROR, 170 "dnssec-policy: bad algorithm %s", 171 alg.base); 172 result = DNS_R_BADALG; 173 goto cleanup; 174 } 175 176 obj = cfg_tuple_get(config, "length"); 177 if (cfg_obj_isuint32(obj)) { 178 uint32_t min, size; 179 size = cfg_obj_asuint32(obj); 180 181 switch (key->algorithm) { 182 case DNS_KEYALG_RSASHA1: 183 case DNS_KEYALG_NSEC3RSASHA1: 184 case DNS_KEYALG_RSASHA256: 185 case DNS_KEYALG_RSASHA512: 186 min = DNS_KEYALG_RSASHA512 ? 1024 : 512; 187 if (size < min || size > 4096) { 188 cfg_obj_log(obj, logctx, ISC_LOG_ERROR, 189 "dnssec-policy: key with " 190 "algorithm %s has invalid " 191 "key length %u", 192 alg.base, size); 193 result = ISC_R_RANGE; 194 goto cleanup; 195 } 196 break; 197 case DNS_KEYALG_ECDSA256: 198 case DNS_KEYALG_ECDSA384: 199 case DNS_KEYALG_ED25519: 200 case DNS_KEYALG_ED448: 201 cfg_obj_log(obj, logctx, ISC_LOG_WARNING, 202 "dnssec-policy: key algorithm %s " 203 "has predefined length; ignoring " 204 "length value %u", 205 alg.base, size); 206 default: 207 break; 208 } 209 210 key->length = size; 211 } 212 } 213 214 dns_kasp_addkey(kasp, key); 215 return (ISC_R_SUCCESS); 216 217cleanup: 218 219 dns_kasp_key_destroy(key); 220 return (result); 221} 222 223static isc_result_t 224cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, 225 isc_log_t *logctx) { 226 dns_kasp_key_t *kkey; 227 unsigned int min_keysize = 4096; 228 const cfg_obj_t *obj = NULL; 229 uint32_t iter = DEFAULT_NSEC3PARAM_ITER; 230 uint32_t saltlen = DEFAULT_NSEC3PARAM_SALTLEN; 231 uint32_t badalg = 0; 232 bool optout = false; 233 isc_result_t ret = ISC_R_SUCCESS; 234 235 /* How many iterations. */ 236 obj = cfg_tuple_get(config, "iterations"); 237 if (cfg_obj_isuint32(obj)) { 238 iter = cfg_obj_asuint32(obj); 239 } 240 dns_kasp_freeze(kasp); 241 for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; 242 kkey = ISC_LIST_NEXT(kkey, link)) 243 { 244 unsigned int keysize = dns_kasp_key_size(kkey); 245 uint32_t keyalg = dns_kasp_key_algorithm(kkey); 246 247 if (keysize < min_keysize) { 248 min_keysize = keysize; 249 } 250 251 /* NSEC3 cannot be used with certain key algorithms. */ 252 if (keyalg == DNS_KEYALG_RSAMD5 || keyalg == DNS_KEYALG_DH || 253 keyalg == DNS_KEYALG_DSA || keyalg == DNS_KEYALG_RSASHA1) 254 { 255 badalg = keyalg; 256 } 257 } 258 dns_kasp_thaw(kasp); 259 260 if (badalg > 0) { 261 char algstr[DNS_SECALG_FORMATSIZE]; 262 dns_secalg_format((dns_secalg_t)badalg, algstr, sizeof(algstr)); 263 cfg_obj_log( 264 obj, logctx, ISC_LOG_ERROR, 265 "dnssec-policy: cannot use nsec3 with algorithm '%s'", 266 algstr); 267 return (DNS_R_NSEC3BADALG); 268 } 269 270 if (iter > dns_nsec3_maxiterations()) { 271 ret = DNS_R_NSEC3ITERRANGE; 272 } 273 274 if (ret == DNS_R_NSEC3ITERRANGE) { 275 cfg_obj_log(obj, logctx, ISC_LOG_ERROR, 276 "dnssec-policy: nsec3 iterations value %u " 277 "out of range", 278 iter); 279 return (ret); 280 } 281 282 /* Opt-out? */ 283 obj = cfg_tuple_get(config, "optout"); 284 if (cfg_obj_isboolean(obj)) { 285 optout = cfg_obj_asboolean(obj); 286 } 287 288 /* Salt */ 289 obj = cfg_tuple_get(config, "salt-length"); 290 if (cfg_obj_isuint32(obj)) { 291 saltlen = cfg_obj_asuint32(obj); 292 } 293 if (saltlen > 0xff) { 294 cfg_obj_log(obj, logctx, ISC_LOG_ERROR, 295 "dnssec-policy: nsec3 salt length %u too high", 296 saltlen); 297 return (DNS_R_NSEC3SALTRANGE); 298 } 299 300 dns_kasp_setnsec3param(kasp, iter, optout, saltlen); 301 return (ISC_R_SUCCESS); 302} 303 304isc_result_t 305cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, 306 isc_mem_t *mctx, isc_log_t *logctx, 307 dns_kasplist_t *kasplist, dns_kasp_t **kaspp) { 308 isc_result_t result; 309 const cfg_obj_t *maps[2]; 310 const cfg_obj_t *koptions = NULL; 311 const cfg_obj_t *keys = NULL; 312 const cfg_obj_t *nsec3 = NULL; 313 const cfg_listelt_t *element = NULL; 314 const char *kaspname = NULL; 315 dns_kasp_t *kasp = NULL; 316 size_t i = 0; 317 uint32_t sigrefresh = 0, sigvalidity = 0; 318 uint32_t dnskeyttl = 0, dsttl = 0, maxttl = 0; 319 uint32_t publishsafety = 0, retiresafety = 0; 320 uint32_t zonepropdelay = 0, parentpropdelay = 0; 321 uint32_t ipub = 0, iret = 0; 322 uint32_t ksk_min_lifetime = 0, zsk_min_lifetime = 0; 323 324 REQUIRE(config != NULL); 325 REQUIRE(kaspp != NULL && *kaspp == NULL); 326 327 kaspname = cfg_obj_asstring(cfg_tuple_get(config, "name")); 328 INSIST(kaspname != NULL); 329 330 cfg_obj_log(config, logctx, ISC_LOG_DEBUG(1), 331 "dnssec-policy: load policy '%s'", kaspname); 332 333 result = dns_kasplist_find(kasplist, kaspname, &kasp); 334 335 if (result == ISC_R_SUCCESS) { 336 cfg_obj_log( 337 config, logctx, ISC_LOG_ERROR, 338 "dnssec-policy: duplicately named policy found '%s'", 339 kaspname); 340 dns_kasp_detach(&kasp); 341 return (ISC_R_EXISTS); 342 } 343 if (result != ISC_R_NOTFOUND) { 344 return (result); 345 } 346 347 /* No kasp with configured name was found in list, create new one. */ 348 INSIST(kasp == NULL); 349 result = dns_kasp_create(mctx, kaspname, &kasp); 350 if (result != ISC_R_SUCCESS) { 351 return (result); 352 } 353 INSIST(kasp != NULL); 354 355 /* Now configure. */ 356 INSIST(DNS_KASP_VALID(kasp)); 357 358 if (config != NULL) { 359 koptions = cfg_tuple_get(config, "options"); 360 maps[i++] = koptions; 361 } 362 maps[i] = NULL; 363 364 /* Configuration: Signatures */ 365 sigrefresh = get_duration(maps, "signatures-refresh", 366 DNS_KASP_SIG_REFRESH); 367 dns_kasp_setsigrefresh(kasp, sigrefresh); 368 369 sigvalidity = get_duration(maps, "signatures-validity-dnskey", 370 DNS_KASP_SIG_VALIDITY_DNSKEY); 371 if (sigrefresh >= (sigvalidity * 0.9)) { 372 cfg_obj_log( 373 config, logctx, ISC_LOG_ERROR, 374 "dnssec-policy: policy '%s' signatures-refresh must be " 375 "at most 90%% of the signatures-validity-dnskey", 376 kaspname); 377 result = ISC_R_FAILURE; 378 } 379 dns_kasp_setsigvalidity_dnskey(kasp, sigvalidity); 380 381 sigvalidity = get_duration(maps, "signatures-validity", 382 DNS_KASP_SIG_VALIDITY); 383 if (sigrefresh >= (sigvalidity * 0.9)) { 384 cfg_obj_log( 385 config, logctx, ISC_LOG_ERROR, 386 "dnssec-policy: policy '%s' signatures-refresh must be " 387 "at most 90%% of the signatures-validity", 388 kaspname); 389 result = ISC_R_FAILURE; 390 } 391 dns_kasp_setsigvalidity(kasp, sigvalidity); 392 393 if (result != ISC_R_SUCCESS) { 394 goto cleanup; 395 } 396 397 /* Configuration: Zone settings */ 398 maxttl = get_duration(maps, "max-zone-ttl", DNS_KASP_ZONE_MAXTTL); 399 dns_kasp_setzonemaxttl(kasp, maxttl); 400 401 zonepropdelay = get_duration(maps, "zone-propagation-delay", 402 DNS_KASP_ZONE_PROPDELAY); 403 dns_kasp_setzonepropagationdelay(kasp, zonepropdelay); 404 405 /* Configuration: Parent settings */ 406 dsttl = get_duration(maps, "parent-ds-ttl", DNS_KASP_DS_TTL); 407 dns_kasp_setdsttl(kasp, dsttl); 408 409 parentpropdelay = get_duration(maps, "parent-propagation-delay", 410 DNS_KASP_PARENT_PROPDELAY); 411 dns_kasp_setparentpropagationdelay(kasp, parentpropdelay); 412 413 /* Configuration: Keys */ 414 dnskeyttl = get_duration(maps, "dnskey-ttl", DNS_KASP_KEY_TTL); 415 dns_kasp_setdnskeyttl(kasp, dnskeyttl); 416 417 publishsafety = get_duration(maps, "publish-safety", 418 DNS_KASP_PUBLISH_SAFETY); 419 dns_kasp_setpublishsafety(kasp, publishsafety); 420 421 retiresafety = get_duration(maps, "retire-safety", 422 DNS_KASP_RETIRE_SAFETY); 423 dns_kasp_setretiresafety(kasp, retiresafety); 424 425 dns_kasp_setpurgekeys( 426 kasp, get_duration(maps, "purge-keys", DNS_KASP_PURGE_KEYS)); 427 428 ipub = dnskeyttl + publishsafety + zonepropdelay; 429 iret = dsttl + retiresafety + parentpropdelay; 430 ksk_min_lifetime = ISC_MAX(ipub, iret); 431 432 iret = (sigvalidity - sigrefresh) + maxttl + retiresafety + 433 zonepropdelay; 434 zsk_min_lifetime = ISC_MAX(ipub, iret); 435 436 (void)confget(maps, "keys", &keys); 437 if (keys != NULL) { 438 char role[256] = { 0 }; 439 bool warn[256][2] = { { false } }; 440 dns_kasp_key_t *kkey = NULL; 441 442 for (element = cfg_list_first(keys); element != NULL; 443 element = cfg_list_next(element)) 444 { 445 cfg_obj_t *kobj = cfg_listelt_value(element); 446 result = cfg_kaspkey_fromconfig(kobj, kasp, logctx, 447 ksk_min_lifetime, 448 zsk_min_lifetime); 449 if (result != ISC_R_SUCCESS) { 450 goto cleanup; 451 } 452 } 453 dns_kasp_freeze(kasp); 454 for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; 455 kkey = ISC_LIST_NEXT(kkey, link)) 456 { 457 uint32_t keyalg = dns_kasp_key_algorithm(kkey); 458 INSIST(keyalg < ARRAY_SIZE(role)); 459 460 if (dns_kasp_key_zsk(kkey)) { 461 if ((role[keyalg] & DNS_KASP_KEY_ROLE_ZSK) != 0) 462 { 463 warn[keyalg][0] = true; 464 } 465 role[keyalg] |= DNS_KASP_KEY_ROLE_ZSK; 466 } 467 468 if (dns_kasp_key_ksk(kkey)) { 469 if ((role[keyalg] & DNS_KASP_KEY_ROLE_KSK) != 0) 470 { 471 warn[keyalg][1] = true; 472 } 473 role[keyalg] |= DNS_KASP_KEY_ROLE_KSK; 474 } 475 } 476 dns_kasp_thaw(kasp); 477 for (i = 0; i < ARRAY_SIZE(role); i++) { 478 if (role[i] == 0) { 479 continue; 480 } 481 if (role[i] != 482 (DNS_KASP_KEY_ROLE_ZSK | DNS_KASP_KEY_ROLE_KSK)) 483 { 484 cfg_obj_log(keys, logctx, ISC_LOG_ERROR, 485 "dnssec-policy: algorithm %zu " 486 "requires both KSK and ZSK roles", 487 i); 488 result = ISC_R_FAILURE; 489 } 490 if (warn[i][0]) { 491 cfg_obj_log(keys, logctx, ISC_LOG_WARNING, 492 "dnssec-policy: algorithm %zu has " 493 "multiple keys with ZSK role", 494 i); 495 } 496 if (warn[i][1]) { 497 cfg_obj_log(keys, logctx, ISC_LOG_WARNING, 498 "dnssec-policy: algorithm %zu has " 499 "multiple keys with KSK role", 500 i); 501 } 502 } 503 if (result != ISC_R_SUCCESS) { 504 goto cleanup; 505 } 506 } else if (default_kasp) { 507 dns_kasp_key_t *key, *new_key; 508 /* 509 * If there are no specific keys configured in the policy, 510 * inherit from the default policy (except for the built-in 511 * "insecure" policy). 512 */ 513 for (key = ISC_LIST_HEAD(dns_kasp_keys(default_kasp)); 514 key != NULL; key = ISC_LIST_NEXT(key, link)) 515 { 516 /* Create a new key reference. */ 517 new_key = NULL; 518 result = dns_kasp_key_create(kasp, &new_key); 519 if (result != ISC_R_SUCCESS) { 520 goto cleanup; 521 } 522 523 if (dns_kasp_key_ksk(key)) { 524 new_key->role |= DNS_KASP_KEY_ROLE_KSK; 525 } 526 if (dns_kasp_key_zsk(key)) { 527 new_key->role |= DNS_KASP_KEY_ROLE_ZSK; 528 } 529 new_key->lifetime = dns_kasp_key_lifetime(key); 530 new_key->algorithm = dns_kasp_key_algorithm(key); 531 new_key->length = dns_kasp_key_size(key); 532 dns_kasp_addkey(kasp, new_key); 533 } 534 } 535 536 if (strcmp(kaspname, "insecure") == 0) { 537 /* "dnssec-policy insecure": key list must be empty */ 538 INSIST(dns_kasp_keylist_empty(kasp)); 539 } else if (default_kasp != NULL) { 540 /* There must be keys configured. */ 541 INSIST(!(dns_kasp_keylist_empty(kasp))); 542 } 543 544 /* Configuration: NSEC3 */ 545 (void)confget(maps, "nsec3param", &nsec3); 546 if (nsec3 == NULL) { 547 if (default_kasp != NULL && dns_kasp_nsec3(default_kasp)) { 548 dns_kasp_setnsec3param( 549 kasp, dns_kasp_nsec3iter(default_kasp), 550 (dns_kasp_nsec3flags(default_kasp) == 0x01), 551 dns_kasp_nsec3saltlen(default_kasp)); 552 } else { 553 dns_kasp_setnsec3(kasp, false); 554 } 555 } else { 556 dns_kasp_setnsec3(kasp, true); 557 result = cfg_nsec3param_fromconfig(nsec3, kasp, logctx); 558 if (result != ISC_R_SUCCESS) { 559 goto cleanup; 560 } 561 } 562 563 /* Append it to the list for future lookups. */ 564 ISC_LIST_APPEND(*kasplist, kasp, link); 565 INSIST(!(ISC_LIST_EMPTY(*kasplist))); 566 567 /* Success: Attach the kasp to the pointer and return. */ 568 dns_kasp_attach(kasp, kaspp); 569 570 /* Don't detach as kasp is on '*kasplist' */ 571 return (ISC_R_SUCCESS); 572 573cleanup: 574 575 /* Something bad happened, detach (destroys kasp) and return error. */ 576 dns_kasp_detach(&kasp); 577 return (result); 578} 579