/* * ldns-signzone signs a zone file * * (c) NLnet Labs, 2005 - 2008 * See the file LICENSE for the license */ #include #include "config.h" #ifdef HAVE_SSL #include #include #include #include #include #include #include #if defined(HAVE_OPENSSL_ENGINE_H) && !defined(OPENSSL_NO_ENGINE) #include #else # ifndef OPENSSL_NO_ENGINE # define OPENSSL_NO_ENGINE # endif #endif #include #define MAX_FILENAME_LEN 250 char *prog; int verbosity = 1; static void usage(FILE *fp, const char *prog) { fprintf(fp, "%s [OPTIONS] zonefile key [key [key]]\n", prog); fprintf(fp, " signs the zone with the given key(s)\n"); fprintf(fp, " -b\t\tuse layout in signed zone and print comments DNSSEC records\n"); fprintf(fp, " -d\t\tused keys are not added to the zone\n"); fprintf(fp, " -e \texpiration date\n"); fprintf(fp, " -f \toutput zone to file (default .signed)\n"); fprintf(fp, " -i \tinception date\n"); fprintf(fp, " -o \torigin for the zone\n"); fprintf(fp, " -u\t\tset SOA serial to the number of seconds since 1-1-1970\n"); fprintf(fp, " -v\t\tprint version and exit\n"); fprintf(fp, " -z <[scheme:]hash>\tAdd ZONEMD resource record\n"); fprintf(fp, "\t\t should be \"simple\" (or 1)\n"); fprintf(fp, "\t\t should be \"sha384\" or \"sha512\" (or 1 or 2)\n"); fprintf(fp, "\t\tthis option can be given more than once\n"); fprintf(fp, " -Z\t\tAllow ZONEMDs to be added without signing\n"); fprintf(fp, " -A\t\tsign DNSKEY with all keys instead of minimal\n"); fprintf(fp, " -U\t\tSign with every unique algorithm in the provided keys\n"); #ifndef OPENSSL_NO_ENGINE fprintf(fp, " -E \tuse as the crypto engine for signing\n"); fprintf(fp, " \tThis can have a lot of extra options, see the manual page for more info\n"); fprintf(fp, " -k ,\tuse `key' with `algorithm' from engine as ZSK\n"); fprintf(fp, " -K ,\tuse `key' with `algorithm' from engine as KSK\n"); #endif fprintf(fp, " -n\t\tuse NSEC3 instead of NSEC.\n"); fprintf(fp, "\t\tIf you use NSEC3, you can specify the following extra options:\n"); fprintf(fp, "\t\t-a [algorithm] hashing algorithm\n"); fprintf(fp, "\t\t-t [number] number of hash iterations\n"); fprintf(fp, "\t\t-s [string] salt\n"); fprintf(fp, "\t\t-p set the opt-out flag on all nsec3 rrs\n"); fprintf(fp, "\n"); fprintf(fp, " keys must be specified by their base name (usually K++),\n"); fprintf(fp, " i.e. WITHOUT the .private extension.\n"); fprintf(fp, " If the public part of the key is not present in the zone, the DNSKEY RR\n"); fprintf(fp, " will be read from the file called .key. If that does not exist,\n"); fprintf(fp, " a default DNSKEY will be generated from the private key and added to the zone.\n"); fprintf(fp, " A date can be a timestamp (seconds since the epoch), or of\n the form \n"); #ifndef OPENSSL_NO_ENGINE fprintf(fp, " For -k or -K, the algorithm can be specified as an integer or a symbolic name:" ); #define __LIST(x) fprintf ( fp, " %3d: %-15s", LDNS_SIGN_ ## x, # x ) fprintf ( fp, "\n " ); __LIST ( RSAMD5 ); #ifdef USE_DSA __LIST ( DSA ); #endif __LIST ( RSASHA1 ); fprintf ( fp, "\n " ); #ifdef USE_DSA __LIST ( DSA_NSEC3 ); #endif __LIST ( RSASHA1_NSEC3 ); __LIST ( RSASHA256 ); fprintf ( fp, "\n " ); __LIST ( RSASHA512 ); __LIST ( ECC_GOST ); __LIST ( ECDSAP256SHA256 ); fprintf ( fp, "\n " ); __LIST ( ECDSAP384SHA384 ); #ifdef USE_ED25519 __LIST ( ED25519 ); #endif #ifdef USE_ED448 __LIST ( ED448 ); #endif fprintf ( fp, "\n" ); #undef __LIST #endif } static void check_tm(struct tm tm) { if (tm.tm_year < 70) { fprintf(stderr, "You cannot specify dates before 1970\n"); exit(EXIT_FAILURE); } if (tm.tm_mon < 0 || tm.tm_mon > 11) { fprintf(stderr, "The month must be in the range 1 to 12\n"); exit(EXIT_FAILURE); } if (tm.tm_mday < 1 || tm.tm_mday > 31) { fprintf(stderr, "The day must be in the range 1 to 31\n"); exit(EXIT_FAILURE); } if (tm.tm_hour < 0 || tm.tm_hour > 23) { fprintf(stderr, "The hour must be in the range 0-23\n"); exit(EXIT_FAILURE); } if (tm.tm_min < 0 || tm.tm_min > 59) { fprintf(stderr, "The minute must be in the range 0-59\n"); exit(EXIT_FAILURE); } if (tm.tm_sec < 0 || tm.tm_sec > 59) { fprintf(stderr, "The second must be in the range 0-59\n"); exit(EXIT_FAILURE); } } /* * if the ttls are different, make them equal * if one of the ttls equals LDNS_DEFAULT_TTL, that one is changed * otherwise, rr2 will get the ttl of rr1 * * prints a warning if a non-default TTL is changed */ static void equalize_ttls(ldns_rr *rr1, ldns_rr *rr2, uint32_t default_ttl) { uint32_t ttl1, ttl2; ttl1 = ldns_rr_ttl(rr1); ttl2 = ldns_rr_ttl(rr2); if (ttl1 != ttl2) { if (ttl1 == default_ttl) { ldns_rr_set_ttl(rr1, ttl2); } else if (ttl2 == default_ttl) { ldns_rr_set_ttl(rr2, ttl1); } else { ldns_rr_set_ttl(rr2, ttl1); fprintf(stderr, "warning: changing non-default TTL %u to %u\n", (unsigned int) ttl2, (unsigned int) ttl1); } } } static void equalize_ttls_rr_list(ldns_rr_list *rr_list, ldns_rr *rr, uint32_t default_ttl) { size_t i; ldns_rr *cur_rr; for (i = 0; i < ldns_rr_list_rr_count(rr_list); i++) { cur_rr = ldns_rr_list_rr(rr_list, i); if (ldns_rr_compare_no_rdata(cur_rr, rr) == 0) { equalize_ttls(cur_rr, rr, default_ttl); } } } static ldns_rr * find_key_in_zone(ldns_rr *pubkey_gen, ldns_zone *zone) { size_t key_i; ldns_rr *pubkey; for (key_i = 0; key_i < ldns_rr_list_rr_count(ldns_zone_rrs(zone)); key_i++) { pubkey = ldns_rr_list_rr(ldns_zone_rrs(zone), key_i); if (ldns_rr_get_type(pubkey) == LDNS_RR_TYPE_DNSKEY && (ldns_calc_keytag(pubkey) == ldns_calc_keytag(pubkey_gen) || /* KSK has gen-keytag + 1 */ ldns_calc_keytag(pubkey) == ldns_calc_keytag(pubkey_gen) + 1) ) { if (verbosity >= 2) { fprintf(stderr, "Found it in the zone!\n"); } return pubkey; } } return NULL; } static ldns_rr * find_key_in_file(const char *keyfile_name_base, ldns_key* ATTR_UNUSED(key), uint32_t zone_ttl) { char *keyfile_name; FILE *keyfile; int line_nr; uint32_t default_ttl = zone_ttl; ldns_rr *pubkey = NULL; keyfile_name = LDNS_XMALLOC(char, strlen(keyfile_name_base) + 5); snprintf(keyfile_name, strlen(keyfile_name_base) + 5, "%s.key", keyfile_name_base); if (verbosity >= 2) { fprintf(stderr, "Trying to read %s\n", keyfile_name); } keyfile = fopen(keyfile_name, "r"); line_nr = 0; if (keyfile) { if (ldns_rr_new_frm_fp_l(&pubkey, keyfile, &default_ttl, NULL, NULL, &line_nr) == LDNS_STATUS_OK) { if (verbosity >= 2) { printf("Key found in file: %s\n", keyfile_name); } } fclose(keyfile); } LDNS_FREE(keyfile_name); return pubkey; } /* this function tries to find the specified keys either in the zone that * has been read, or in a .key file. If the key is not found, * a public key is generated, and it is assumed the key is a ZSK * * if add_keys is true; the DNSKEYs are added to the zone prior to signing * if it is false, they are not added. * Even if keys are not added, the function is still needed, to check * whether keys of which we only have key data are KSKs or ZSKS */ static void find_or_create_pubkey(const char *keyfile_name_base, ldns_key *key, ldns_zone *orig_zone, bool add_keys, uint32_t default_ttl) { ldns_rr *pubkey_gen, *pubkey; int key_in_zone; if (default_ttl == LDNS_DEFAULT_TTL) { default_ttl = ldns_rr_ttl(ldns_zone_soa(orig_zone)); } if (!ldns_key_pubkey_owner(key)) { ldns_key_set_pubkey_owner(key, ldns_rdf_clone(ldns_rr_owner(ldns_zone_soa(orig_zone)))); } /* find the public key in the zone, or in a * separate file * we 'generate' one anyway, * then match that to any present in the zone, * if it matches, we drop our own. If not, * we try to see if there is a .key file present. * If not, we use our own generated one, with * some default values * * Even if -d (do-not-add-keys) is specified, * we still need to do this, because we need * to have any key flags that are set this way */ pubkey_gen = ldns_key2rr(key); ldns_rr_set_ttl(pubkey_gen, default_ttl); if (verbosity >= 2) { fprintf(stderr, "Looking for key with keytag %u or %u\n", (unsigned int) ldns_calc_keytag(pubkey_gen), (unsigned int) ldns_calc_keytag(pubkey_gen)+1 ); } pubkey = find_key_in_zone(pubkey_gen, orig_zone); key_in_zone = 1; if (!pubkey) { key_in_zone = 0; /* it was not in the zone, try to read a .key file */ pubkey = find_key_in_file(keyfile_name_base, key, default_ttl); if (!pubkey && !(ldns_key_flags(key) & LDNS_KEY_SEP_KEY)) { /* maybe it is a ksk? */ ldns_key_set_keytag(key, ldns_key_keytag(key) + 1); pubkey = find_key_in_file(keyfile_name_base, key, default_ttl); if (!pubkey) { /* ok, no file, set back to ZSK */ ldns_key_set_keytag(key, ldns_key_keytag(key) - 1); } } if(pubkey && ldns_dname_compare(ldns_rr_owner(pubkey), ldns_rr_owner(ldns_zone_soa(orig_zone))) != 0) { fprintf(stderr, "Error %s.key has wrong name: %s\n", keyfile_name_base, ldns_rdf2str(ldns_rr_owner(pubkey))); exit(EXIT_FAILURE); /* leak rdf2str, but we exit */ } } if (!pubkey) { /* okay, no public key found, just use our generated one */ pubkey = pubkey_gen; if (verbosity >= 2) { fprintf(stderr, "Not in zone, no .key file, generating ZSK DNSKEY from private key data\n"); } } else { ldns_rr_free(pubkey_gen); } ldns_key_set_flags(key, ldns_rdf2native_int16(ldns_rr_rdf(pubkey, 0))); ldns_key_set_keytag(key, ldns_calc_keytag(pubkey)); if (add_keys && !key_in_zone) { equalize_ttls_rr_list(ldns_zone_rrs(orig_zone), pubkey, default_ttl); ldns_zone_push_rr(orig_zone, pubkey); } } #ifndef OPENSSL_NO_ENGINE /* * For keys coming from the engine (-k or -K), parse algorithm specification. */ static enum ldns_enum_signing_algorithm parse_algspec ( const char * const p ) { if ( p == NULL ) return 0; if ( isdigit ( (const unsigned char)*p ) ) { const char *nptr = NULL; const long id = strtol ( p, (char **) &nptr, 10 ); return id > 0 && nptr != NULL && *nptr == ',' ? id : 0; } #define __MATCH(x) \ if ( !memcmp ( # x, p, sizeof ( # x ) - 1 ) \ && p [ sizeof ( # x ) - 1 ] == ',' ) { \ return LDNS_SIGN_ ## x; \ } __MATCH ( RSAMD5 ); __MATCH ( RSASHA1 ); #ifdef USE_DSA __MATCH ( DSA ); #endif __MATCH ( RSASHA1_NSEC3 ); __MATCH ( RSASHA256 ); __MATCH ( RSASHA512 ); #ifdef USE_DSA __MATCH ( DSA_NSEC3 ); #endif __MATCH ( ECC_GOST ); __MATCH ( ECDSAP256SHA256 ); __MATCH ( ECDSAP384SHA384 ); #ifdef USE_ED25519 __MATCH ( ED25519 ); #endif #ifdef USE_ED448 __MATCH ( ED448 ); #endif #undef __MATCH return 0; } /* * For keys coming from the engine (-k or -K), parse key specification * in the form of ,. No whitespace is allowed * between and the comma, and between the comma and * . format is specific to the engine at hand, i.e. * it can be the old OpenSC syntax or a PKCS #11 URI as defined in RFC 7512 * and (partially) supported by OpenSC (as of 20180312). */ static const char * parse_keyspec ( const char * const p, enum ldns_enum_signing_algorithm * const algorithm, const char ** const id ) { const char * const comma = strchr ( p, ',' ); if ( comma == NULL || !(*algorithm = parse_algspec ( p )) ) return NULL; return comma [ 1 ] ? *id = comma + 1 : NULL; } /* * Load a key from the engine. */ static ldns_key * load_key ( const char * const p, ENGINE * const e ) { enum ldns_enum_signing_algorithm alg = 0; const char *id = NULL; ldns_status status = LDNS_STATUS_ERR; ldns_key *key = NULL; /* Parse key specification. */ if ( parse_keyspec ( p, &alg, &id ) == NULL ) { fprintf ( stderr, "Failed to parse key specification `%s'.\n", p ); usage ( stderr, prog ); exit ( EXIT_FAILURE ); } /* Validate that the algorithm can be used for signing. */ switch ( alg ) { case LDNS_SIGN_RSAMD5: case LDNS_SIGN_RSASHA1: case LDNS_SIGN_RSASHA1_NSEC3: case LDNS_SIGN_RSASHA256: case LDNS_SIGN_RSASHA512: #ifdef USE_DSA case LDNS_SIGN_DSA: case LDNS_SIGN_DSA_NSEC3: #endif case LDNS_SIGN_ECC_GOST: #ifdef USE_ECDSA case LDNS_SIGN_ECDSAP256SHA256: case LDNS_SIGN_ECDSAP384SHA384: #endif break; default: fprintf ( stderr, "Algorithm %d cannot be used for signing.\n", alg ); usage ( stderr, prog ); exit ( EXIT_FAILURE ); } printf ( "Engine key id: %s, algo %d\n", id, alg ); /* Attempt to load the key from the engine. */ status = ldns_key_new_frm_engine ( &key, e, (char *) id, (ldns_algorithm)alg ); if ( status != LDNS_STATUS_OK ) { ERR_print_errors_fp ( stderr ); exit ( EXIT_FAILURE ); } return key; } /* * For keys coming from the engine (-k or -K), set key parameters * and determine whether the key is listed in the zone file. */ static void post_process_engine_key ( ldns_key_list * const keys, ldns_key * const key, ldns_zone * const zone, const bool add_keys, const uint32_t ttl, const uint32_t inception, const uint32_t expiration ) { if ( key == NULL ) return; if ( expiration ) ldns_key_set_expiration ( key, expiration ); if ( inception ) ldns_key_set_inception ( key, inception ); ldns_key_list_push_key ( keys, key ); find_or_create_pubkey ( "", key, zone, add_keys, ttl ); } /* * Initialize OpenSSL, for versions 1.1 and newer. */ static ENGINE * init_openssl_engine ( const char * const id ) { ENGINE *e = NULL; #ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS ERR_load_crypto_strings(); #endif #if OPENSSL_VERSION_NUMBER < 0x10100000 || defined(HAVE_LIBRESSL) || !defined(HAVE_OPENSSL_INIT_CRYPTO) OpenSSL_add_all_algorithms(); #else if ( !OPENSSL_init_crypto ( OPENSSL_INIT_LOAD_CONFIG, NULL ) ) { fprintf ( stderr, "OPENSSL_init_crypto(3) failed.\n" ); ERR_print_errors_fp ( stderr ); exit ( EXIT_FAILURE ); } #endif if ( (e = ENGINE_by_id ( id )) == NULL ) { fprintf ( stderr, "ENGINE_by_id(3) failed.\n" ); ERR_print_errors_fp ( stderr ); exit ( EXIT_FAILURE ); } if ( !ENGINE_set_default_DSA ( e ) ) { fprintf ( stderr, "ENGINE_set_default_DSA(3) failed.\n" ); ERR_print_errors_fp ( stderr ); exit ( EXIT_FAILURE ); } if ( !ENGINE_set_default_RSA ( e ) ) { fprintf ( stderr, "ENGINE_set_default_RSA(3) failed.\n" ); ERR_print_errors_fp ( stderr ); exit ( EXIT_FAILURE ); } return e; } /* * De-initialize OpenSSL, for versions 1.1 and newer. * * All of that is not strictly necessary because the process exits * anyway, however, when an engine is used, this is the only hope * of letting the engine's driver know that the program terminates * (for the fear that the driver's reference counting may go awry, etc.) * Still, there is no guarantee that this function helps... */ static void shutdown_openssl ( ENGINE * const e ) { if ( e != NULL ) { #ifdef HAVE_ENGINE_FREE ENGINE_free ( e ); #endif #ifdef HAVE_ENGINE_CLEANUP ENGINE_cleanup (); #endif } #ifdef HAVE_CONF_MODULES_UNLOAD CONF_modules_unload ( 1 ); #endif #ifdef HAVE_EVP_CLEANUP EVP_cleanup (); #endif #ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA CRYPTO_cleanup_all_ex_data (); #endif #ifdef HAVE_ERR_FREE_STRINGS ERR_free_strings (); #endif } #endif int str2zonemd_signflag(const char *str, const char **reason) { char *colon; static const char *reasons[] = { "Unknown , should be \"simple\"" , "Syntax error in , should be \"sha384\" or \"sha512\"" , "Unknown , should be \"sha384\" or \"sha512\"" }; if (!str) return LDNS_STATUS_NULL; if ((colon = strchr(str, ':'))) { if ((colon - str != 1 || str[0] != '1') && (colon - str != 6 || strncasecmp(str, "simple", 6))) { if (reason) *reason = reasons[0]; return 0; } if (strchr(colon + 1, ':')) { if (reason) *reason = reasons[1]; return 0; } return str2zonemd_signflag(colon + 1, reason); } if (!strcasecmp(str, "1") || !strcasecmp(str, "sha384")) return LDNS_SIGN_WITH_ZONEMD_SIMPLE_SHA384; if (!strcasecmp(str, "2") || !strcasecmp(str, "sha512")) return LDNS_SIGN_WITH_ZONEMD_SIMPLE_SHA512; if (reason) *reason = reasons[2]; return 0; } int main(int argc, char *argv[]) { const char *zonefile_name; FILE *zonefile = NULL; int line_nr = 0; int c; int argi; #ifndef OPENSSL_NO_ENGINE ENGINE *engine = NULL; #endif ldns_zone *orig_zone; ldns_rr_list *orig_rrs = NULL; ldns_rr *orig_soa = NULL; ldns_dnssec_zone *signed_zone; char *keyfile_name_base; char *keyfile_name = NULL; FILE *keyfile = NULL; ldns_key *key = NULL; #ifndef OPENSSL_NO_ENGINE ldns_key *eng_ksk = NULL; /* KSK specified with -K */ ldns_key *eng_zsk = NULL; /* ZSK specified with -k */ #endif ldns_key_list *keys; ldns_status s; size_t i; ldns_rr_list *added_rrs; char *outputfile_name = NULL; FILE *outputfile; bool use_nsec3 = false; int signflags = 0; bool unixtime_serial = false; /* Add the given keys to the zone if they are not yet present */ bool add_keys = true; uint8_t nsec3_algorithm = 1; uint8_t nsec3_flags = 0; size_t nsec3_iterations_cmd = 1; uint16_t nsec3_iterations = 1; uint8_t nsec3_salt_length = 0; uint8_t *nsec3_salt = NULL; /* we need to know the origin before reading ksk's, * so keep an array of filenames until we know it */ struct tm tm; uint32_t inception; uint32_t expiration; ldns_rdf *origin = NULL; uint32_t ttl = LDNS_DEFAULT_TTL; ldns_rr_class class = LDNS_RR_CLASS_IN; ldns_status result; ldns_output_format_storage fmt_st; ldns_output_format* fmt = ldns_output_format_init(&fmt_st); /* For parson zone digest parameters */ int flag; const char *reason = NULL; prog = strdup(argv[0]); inception = 0; expiration = 0; keys = ldns_key_list_new(); while ((c = getopt(argc, argv, "a:bde:f:i:k:no:ps:t:uvz:ZAUE:K:")) != -1) { switch (c) { case 'a': nsec3_algorithm = (uint8_t) atoi(optarg); if (nsec3_algorithm != 1) { fprintf(stderr, "Bad NSEC3 algorithm, only RSASHA1 allowed\n"); exit(EXIT_FAILURE); } break; case 'b': ldns_output_format_set(fmt, LDNS_COMMENT_FLAGS | LDNS_COMMENT_LAYOUT | LDNS_COMMENT_NSEC3_CHAIN | LDNS_COMMENT_BUBBLEBABBLE); break; case 'd': add_keys = false; break; case 'e': /* try to parse YYYYMMDD first, * if that doesn't work, it * should be a timestamp (seconds since epoch) */ memset(&tm, 0, sizeof(tm)); if (strlen(optarg) == 8 && sscanf(optarg, "%4d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday) ) { tm.tm_year -= 1900; tm.tm_mon--; check_tm(tm); expiration = (uint32_t) ldns_mktime_from_utc(&tm); } else if (strlen(optarg) == 14 && sscanf(optarg, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) ) { tm.tm_year -= 1900; tm.tm_mon--; check_tm(tm); expiration = (uint32_t) ldns_mktime_from_utc(&tm); } else { expiration = (uint32_t) atol(optarg); } break; case 'f': outputfile_name = LDNS_XMALLOC(char, MAX_FILENAME_LEN + 1); strncpy(outputfile_name, optarg, MAX_FILENAME_LEN); break; case 'i': memset(&tm, 0, sizeof(tm)); if (strlen(optarg) == 8 && sscanf(optarg, "%4d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday) ) { tm.tm_year -= 1900; tm.tm_mon--; check_tm(tm); inception = (uint32_t) ldns_mktime_from_utc(&tm); } else if (strlen(optarg) == 14 && sscanf(optarg, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) ) { tm.tm_year -= 1900; tm.tm_mon--; check_tm(tm); inception = (uint32_t) ldns_mktime_from_utc(&tm); } else { inception = (uint32_t) atol(optarg); } break; case 'n': use_nsec3 = true; break; case 'o': if (ldns_str2rdf_dname(&origin, optarg) != LDNS_STATUS_OK) { fprintf(stderr, "Bad origin, not a correct domain name\n"); usage(stderr, prog); exit(EXIT_FAILURE); } break; case 'p': nsec3_flags = nsec3_flags | LDNS_NSEC3_VARS_OPTOUT_MASK; break; case 'u': unixtime_serial = true; break; case 'v': printf("zone signer version %s (ldns version %s)\n", LDNS_VERSION, ldns_version()); exit(EXIT_SUCCESS); break; case 'z': flag = str2zonemd_signflag(optarg, &reason); if (flag) signflags |= flag; else { fprintf( stderr , "%s\nwith zone digest parameters:" " \"%s\"\n" , reason, optarg); exit(EXIT_FAILURE); } break; case 'Z': signflags |= LDNS_SIGN_NO_KEYS_NO_NSECS; break; case 'A': signflags |= LDNS_SIGN_DNSKEY_WITH_ZSK; break; case 'E': #ifndef OPENSSL_NO_ENGINE engine = init_openssl_engine ( optarg ); break; #else /* fallthrough */ #endif case 'k': #ifndef OPENSSL_NO_ENGINE eng_zsk = load_key ( optarg, engine ); break; #else /* fallthrough */ #endif case 'K': #ifndef OPENSSL_NO_ENGINE eng_ksk = load_key ( optarg, engine ); /* I apologize for that, there is no API. */ eng_ksk -> _extra.dnssec.flags |= LDNS_KEY_SEP_KEY; #else fprintf(stderr, "%s compiled without engine support\n" , prog); exit(EXIT_FAILURE); #endif break; case 'U': signflags |= LDNS_SIGN_WITH_ALL_ALGORITHMS; break; case 's': if (strlen(optarg) % 2 != 0) { fprintf(stderr, "Salt value is not valid hex data, not a multiple of 2 characters\n"); exit(EXIT_FAILURE); } nsec3_salt_length = (uint8_t) strlen(optarg) / 2; nsec3_salt = LDNS_XMALLOC(uint8_t, nsec3_salt_length); for (c = 0; c < (int) strlen(optarg); c += 2) { if (isxdigit((int) optarg[c]) && isxdigit((int) optarg[c+1])) { nsec3_salt[c/2] = (uint8_t) ldns_hexdigit_to_int(optarg[c]) * 16 + ldns_hexdigit_to_int(optarg[c+1]); } else { fprintf(stderr, "Salt value is not valid hex data.\n"); exit(EXIT_FAILURE); } } break; case 't': nsec3_iterations_cmd = (size_t) atol(optarg); if (nsec3_iterations_cmd > LDNS_NSEC3_MAX_ITERATIONS) { fprintf(stderr, "Iterations count can not exceed %u, quitting\n", LDNS_NSEC3_MAX_ITERATIONS); exit(EXIT_FAILURE); } nsec3_iterations = (uint16_t) nsec3_iterations_cmd; break; default: usage(stderr, prog); exit(EXIT_SUCCESS); } } argc -= optind; argv += optind; if (argc < 1) { printf("Error: not enough arguments\n"); usage(stdout, prog); exit(EXIT_FAILURE); } else { zonefile_name = argv[0]; } /* read zonefile first to find origin if not specified */ if (strncmp(zonefile_name, "-", 2) == 0) { s = ldns_zone_new_frm_fp_l(&orig_zone, stdin, origin, ttl, class, &line_nr); if (s != LDNS_STATUS_OK) { fprintf(stderr, "Zone not read, error: %s at stdin line %d\n", ldns_get_errorstr_by_id(s), line_nr); exit(EXIT_FAILURE); } else { orig_soa = ldns_zone_soa(orig_zone); if (!orig_soa) { fprintf(stderr, "Error reading zonefile: missing SOA record\n"); exit(EXIT_FAILURE); } orig_rrs = ldns_zone_rrs(orig_zone); if (!orig_rrs) { fprintf(stderr, "Error reading zonefile: no resource records\n"); exit(EXIT_FAILURE); } } } else { zonefile = fopen(zonefile_name, "r"); if (!zonefile) { fprintf(stderr, "Error: unable to read %s (%s)\n", zonefile_name, strerror(errno)); exit(EXIT_FAILURE); } else { s = ldns_zone_new_frm_fp_l(&orig_zone, zonefile, origin, ttl, class, &line_nr); if (s != LDNS_STATUS_OK) { fprintf(stderr, "Zone not read, error: %s at %s line %d\n", ldns_get_errorstr_by_id(s), zonefile_name, line_nr); exit(EXIT_FAILURE); } else { orig_soa = ldns_zone_soa(orig_zone); if (!orig_soa) { fprintf(stderr, "Error reading zonefile: missing SOA record\n"); exit(EXIT_FAILURE); } orig_rrs = ldns_zone_rrs(orig_zone); if (!orig_rrs) { fprintf(stderr, "Error reading zonefile: no resource records\n"); exit(EXIT_FAILURE); } } fclose(zonefile); } } /* read the ZSKs */ argi = 1; while (argi < argc) { keyfile_name_base = argv[argi]; keyfile_name = LDNS_XMALLOC(char, strlen(keyfile_name_base) + 9); snprintf(keyfile_name, strlen(keyfile_name_base) + 9, "%s.private", keyfile_name_base); keyfile = fopen(keyfile_name, "r"); line_nr = 0; if (!keyfile) { fprintf(stderr, "Error: unable to read %s: %s\n", keyfile_name, strerror(errno)); } else { s = ldns_key_new_frm_fp_l(&key, keyfile, &line_nr); fclose(keyfile); if (s == LDNS_STATUS_OK) { /* set times in key? they will end up in the rrsigs */ if (expiration != 0) { ldns_key_set_expiration(key, expiration); } if (inception != 0) { ldns_key_set_inception(key, inception); } LDNS_FREE(keyfile_name); ldns_key_list_push_key(keys, key); } else { fprintf(stderr, "Error reading key from %s at line %d: %s\n", argv[argi], line_nr, ldns_get_errorstr_by_id(s)); } } /* and, if not unset by -p, find or create the corresponding DNSKEY record */ if (key) { find_or_create_pubkey(keyfile_name_base, key, orig_zone, add_keys, ttl); } argi++; } #ifndef OPENSSL_NO_ENGINE /* * The user may have loaded a KSK and a ZSK from the engine. * Since these keys carry no meta-information which is * relevant to DNS (origin, TTL, etc), and because that * information becomes known only after the command line * and the zone file are parsed completely, the program * needs to post-process these keys before they become usable. */ /* The engine's KSK. */ post_process_engine_key ( keys, eng_ksk, orig_zone, add_keys, ttl, inception, expiration ); /* The engine's ZSK. */ post_process_engine_key ( keys, eng_zsk, orig_zone, add_keys, ttl, inception, expiration ); #endif if (ldns_key_list_key_count(keys) < 1 && !(signflags & LDNS_SIGN_NO_KEYS_NO_NSECS)) { fprintf(stderr, "Error: no keys to sign with. Aborting.\n\n"); usage(stderr, prog); exit(EXIT_FAILURE); } signed_zone = ldns_dnssec_zone_new(); if (unixtime_serial) { ldns_rr_soa_increment_func_int(ldns_zone_soa(orig_zone), ldns_soa_serial_unixtime, 0); } if (ldns_dnssec_zone_add_rr(signed_zone, ldns_zone_soa(orig_zone)) != LDNS_STATUS_OK) { fprintf(stderr, "Error adding SOA to dnssec zone, skipping record\n"); } for (i = 0; i < ldns_rr_list_rr_count(ldns_zone_rrs(orig_zone)); i++) { if (ldns_dnssec_zone_add_rr(signed_zone, ldns_rr_list_rr(ldns_zone_rrs(orig_zone), i)) != LDNS_STATUS_OK) { fprintf(stderr, "Error adding RR to dnssec zone"); fprintf(stderr, ", skipping record:\n"); ldns_rr_print(stderr, ldns_rr_list_rr(ldns_zone_rrs(orig_zone), i)); } } /* list to store newly created rrs, so we can free them later */ added_rrs = ldns_rr_list_new(); if (use_nsec3) { if (verbosity < 1) ; /* pass */ else if (nsec3_iterations > 500) fprintf(stderr, "Warning! NSEC3 iterations larger than " "500 may cause validating resolvers to return " "SERVFAIL!\n" "See: https://datatracker.ietf.org/doc/html/" "draft-hardaker-dnsop-nsec3-guidance-03#section-4\n"); else if (nsec3_iterations > 100) fprintf(stderr, "Warning! NSEC3 iterations larger than " "100 may cause validating resolvers to return " "insecure responses!\n" "See: https://datatracker.ietf.org/doc/html/" "draft-hardaker-dnsop-nsec3-guidance-03#section-4\n"); result = ldns_dnssec_zone_sign_nsec3_flg_mkmap(signed_zone, added_rrs, keys, ldns_dnssec_default_replace_signatures, NULL, nsec3_algorithm, nsec3_flags, nsec3_iterations, nsec3_salt_length, nsec3_salt, signflags, &fmt_st.hashmap); } else { result = ldns_dnssec_zone_sign_flg(signed_zone, added_rrs, keys, ldns_dnssec_default_replace_signatures, NULL, signflags); } if (result != LDNS_STATUS_OK) { fprintf(stderr, "Error signing zone: %s\n", ldns_get_errorstr_by_id(result)); } if (!outputfile_name) { outputfile_name = LDNS_XMALLOC(char, MAX_FILENAME_LEN); snprintf(outputfile_name, MAX_FILENAME_LEN, "%s.signed", zonefile_name); } if (signed_zone) { if (strncmp(outputfile_name, "-", 2) == 0) { ldns_dnssec_zone_print(stdout, signed_zone); } else { outputfile = fopen(outputfile_name, "w"); if (!outputfile) { fprintf(stderr, "Unable to open %s for writing: %s\n", outputfile_name, strerror(errno)); } else { ldns_dnssec_zone_print_fmt( outputfile, fmt, signed_zone); fclose(outputfile); } } } else { fprintf(stderr, "Error signing zone.\n"); #ifdef HAVE_SSL if (ERR_peek_error()) { #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(HAVE_LIBRESSL) #ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS ERR_load_crypto_strings(); #endif #endif ERR_print_errors_fp(stderr); #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(HAVE_LIBRESSL) #ifdef HAVE_ERR_FREE_STRINGS ERR_free_strings (); #endif #endif } #endif exit(EXIT_FAILURE); } ldns_key_list_free(keys); /* since the ldns_rr records are pointed to in both the ldns_zone * and the ldns_dnssec_zone, we can either deep_free the * dnssec_zone and 'shallow' free the original zone and added * records, or the other way around */ ldns_dnssec_zone_free(signed_zone); ldns_zone_deep_free(orig_zone); ldns_rr_list_deep_free(added_rrs); ldns_rdf_deep_free(origin); LDNS_FREE(outputfile_name); #ifndef OPENSSL_NO_ENGINE shutdown_openssl ( engine ); #else #if OPENSSL_VERSION_NUMBER < 0x10100000 || defined(HAVE_LIBRESSL) #ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA CRYPTO_cleanup_all_ex_data (); #endif #endif #endif free(prog); exit(EXIT_SUCCESS); } #else /* !HAVE_SSL */ int main(int argc __attribute__((unused)), char **argv __attribute__((unused))) { fprintf(stderr, "ldns-signzone needs OpenSSL support, which has not been compiled in\n"); return 1; } #endif /* HAVE_SSL */