]> git.ipfire.org Git - thirdparty/ldns.git/commitdiff
ldns-signzone: Complete engine support.
authorVadim Penzin <vadim@penzin.net>
Tue, 13 Mar 2018 21:22:23 +0000 (23:22 +0200)
committerWillem Toorop <willem@nlnetlabs.nl>
Thu, 15 Mar 2018 09:03:47 +0000 (10:03 +0100)
examples/ldns-signzone.c

index 2adc9431796646c5a123426083318aea827584ac..0a790e66f8dbe663afd59eb84932875339101d61 100644 (file)
@@ -3,9 +3,17 @@
  * 
  * (c) NLnet Labs, 2005 - 2008
  * See the file LICENSE for the license
+ *
+ * Portions of engine support by Vadim Penzin <vadim@penzin.net>, 2018.
+ * I hereby place my work on this program into the public domain.
  */
 
+#include <stdio.h>
+
 #include "config.h"
+
+#ifdef HAVE_SSL
+
 #include <stdlib.h>
 #include <unistd.h>
 
 #include <ldns/ldns.h>
 #include <ldns/keys.h>
 
-#ifdef HAVE_SSL
 #include <openssl/conf.h>
 #include <openssl/engine.h>
-#endif /* HAVE_SSL */
+#include <openssl/err.h>
 
 #define MAX_FILENAME_LEN 250
-int verbosity = 1;
 
-#ifdef HAVE_SSL
-#include <openssl/err.h>
+char *prog;
+int verbosity = 1;
 
 static void
 usage(FILE *fp, const char *prog) {
@@ -42,9 +48,8 @@ usage(FILE *fp, const char *prog) {
        fprintf(fp, "  -U\t\tSign with every unique algorithm in the provided keys\n");
        fprintf(fp, "  -E <name>\tuse <name> 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 <id>,<int>\tuse key id with algorithm int from engine\n");
-       fprintf(fp, "  -K <id>,<int>\tuse key id with algorithm int from engine as KSK\n");
-       fprintf(fp, "\t\tif no key is given (but an external one is used through the engine support, it might be necessary to provide the right algorithm number.\n");
+       fprintf(fp, "  -k <algorithm>,<key>\tuse `key' with `algorithm' from engine as ZSK\n");
+       fprintf(fp, "  -K <algorithm>,<key>\tuse `key' with `algorithm' from engine as KSK\n");
        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");
@@ -58,6 +63,35 @@ usage(FILE *fp, const char *prog) {
        fprintf(fp, "  will be read from the file called <base name>.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 <YYYYMMdd[hhmmss]>\n");
+       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 );
+       __LIST ( DSA );
+       __LIST ( RSASHA1 );
+       fprintf ( fp, "\n " );
+       __LIST ( DSA_NSEC3 );
+       __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
 }
 
 static void check_tm(struct tm tm)
@@ -288,6 +322,207 @@ find_or_create_pubkey(const char *keyfile_name_base, ldns_key *key, ldns_zone *o
        }
 }
 
+/*
+ * For keys coming from the engine (-k or -K), parse algoritm specification.
+ */
+static enum ldns_enum_signing_algorithm
+parse_algspec ( const char * const p )
+{
+       if ( p == NULL )
+               return 0;
+
+       if ( isdigit ( *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 );
+       __MATCH ( DSA );
+       __MATCH ( RSASHA1_NSEC3 );
+       __MATCH ( RSASHA256 );
+       __MATCH ( RSASHA512 );
+       __MATCH ( DSA_NSEC3 );
+       __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 <algorithm>,<key-id>. No whitespace is allowed
+ * between <algorithm> and the comma, and between the comma and
+ * <key-id>. <key-id> 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:
+       case LDNS_SIGN_DSA:
+       case LDNS_SIGN_DSA_NSEC3:
+       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, 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;
+
+       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 );
+       }
+
+       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 ) {
+               ENGINE_free ( e );
+               ENGINE_cleanup ();
+       }
+
+       CONF_modules_unload ( 1 );
+       EVP_cleanup ();
+       CRYPTO_cleanup_all_ex_data ();
+       ERR_free_strings ();
+}
+
 int
 main(int argc, char *argv[])
 {
@@ -307,6 +542,8 @@ main(int argc, char *argv[])
        char *keyfile_name = NULL;
        FILE *keyfile = NULL;
        ldns_key *key = NULL;
+       ldns_key *eng_ksk = NULL; /* KSK specified with -K */
+       ldns_key *eng_zsk = NULL; /* ZSK specified with -k */
        ldns_key_list *keys;
        ldns_status s;
        size_t i;
@@ -315,12 +552,6 @@ main(int argc, char *argv[])
        char *outputfile_name = NULL;
        FILE *outputfile;
        
-       /* tmp vars for engine keys */
-       char *eng_key_l;
-       size_t eng_key_id_len;
-       char *eng_key_id;
-       int eng_key_algo;
-       
        bool use_nsec3 = false;
        int signflags = 0;
 
@@ -343,12 +574,12 @@ main(int argc, char *argv[])
        uint32_t ttl = LDNS_DEFAULT_TTL;
        ldns_rr_class class = LDNS_RR_CLASS_IN; 
        
-       char *prog = strdup(argv[0]);
        ldns_status result;
 
        ldns_output_format_storage fmt_st;
        ldns_output_format* fmt = ldns_output_format_init(&fmt_st);
        
+       prog = strdup(argv[0]);
        inception = 0;
        expiration = 0;
        
@@ -447,103 +678,15 @@ main(int argc, char *argv[])
                        signflags |= LDNS_SIGN_DNSKEY_WITH_ZSK;
                        break;
                case 'E':
-                       ENGINE_load_builtin_engines();
-                       ENGINE_load_dynamic();
-#ifdef HAVE_ENGINE_LOAD_CRYPTODEV
-                       ENGINE_load_cryptodev();
-#endif
-                       engine = ENGINE_by_id(optarg);
-                       if (!engine) {
-                               printf("No such engine: %s\n", optarg);
-                               engine = ENGINE_get_first();
-                               printf("Available engines:\n");
-                               while (engine) {
-                                       printf("%s\n", ENGINE_get_id(engine));
-                                       engine = ENGINE_get_next(engine);
-                               }
-                               exit(EXIT_FAILURE);
-                       } else {
-                               if (!ENGINE_init(engine)) {
-                                       printf("The engine couldn't initialize\n");
-                                       exit(EXIT_FAILURE);
-                               }
-                               ENGINE_set_default_RSA(engine);
-                               ENGINE_set_default_DSA(engine);
-                               ENGINE_set_default(engine, 0);
-                       }
+                       engine = init_openssl_engine ( optarg );
                        break;
                case 'k':
-                       eng_key_l = strchr(optarg, ',');
-                       if (eng_key_l && strlen(eng_key_l) > 1) {
-                               if (eng_key_l > optarg) {
-                                       eng_key_id_len = (size_t) (eng_key_l - optarg);
-                                       eng_key_id = malloc(eng_key_id_len + 1);
-                                       memcpy(eng_key_id, optarg, eng_key_id_len);
-                                       eng_key_id[eng_key_id_len] = '\0';
-                               } else {
-                                       /* no id given, use default from engine */
-                                       eng_key_id = NULL;
-                               }
-
-                               eng_key_algo = atoi(eng_key_l + 1);
-
-                               printf("Engine key id: %s, algo %d\n", eng_key_id, eng_key_algo);
-
-                               s = ldns_key_new_frm_engine(&key, engine, eng_key_id, eng_key_algo);
-                               if (s == LDNS_STATUS_OK) {
-                                       /* must be dnssec key */
-                                       switch (ldns_key_algorithm(key)) {
-                                       case LDNS_SIGN_RSAMD5:
-                                       case LDNS_SIGN_RSASHA1:
-                                       case LDNS_SIGN_RSASHA1_NSEC3:
-                                       case LDNS_SIGN_RSASHA256:
-                                       case LDNS_SIGN_RSASHA512:
-                                       case LDNS_SIGN_DSA:
-                                       case LDNS_SIGN_DSA_NSEC3:
-                                       case LDNS_SIGN_ECC_GOST:
-#ifdef USE_ECDSA
-                                       case LDNS_SIGN_ECDSAP256SHA256:
-                                       case LDNS_SIGN_ECDSAP384SHA384:
-#endif
-                                               ldns_key_list_push_key(keys, key);
-                                               /*printf("Added key at %p:\n", key);*/
-                                               /*ldns_key_print(stdout, key);*/
-                                               break;
-                                       default:
-                                               fprintf(stderr, "Warning, key not suitable for signing, ignoring key with algorithm %u\n", ldns_key_algorithm(key));
-                                               break;
-                                       }
-                                       if (expiration != 0) {
-                                               ldns_key_set_expiration(key,
-                                                               expiration);
-                                       }
-                                       if (inception != 0) {
-                                               ldns_key_set_inception(key,
-                                                               inception);
-                                       }
-                               } else {
-                                       printf("Error reading key '%s' from engine: %s\n", eng_key_id, ldns_get_errorstr_by_id(s));
-                                       #ifdef HAVE_SSL
-                                                       if (ERR_peek_error()) {
-                                                               ERR_load_crypto_strings();
-                                                               ERR_print_errors_fp(stderr);
-                                                               ERR_free_strings();
-                                                       }
-                                       #endif
-                                       exit(EXIT_FAILURE);
-                               }
-
-                               if (eng_key_id) {
-                                       free(eng_key_id);
-                               }
-                       } else {
-                               printf("Error: bad engine key specification (should be: -k <id>,<algorithm>)).\n");
-                               exit(EXIT_FAILURE);
-                       }
+                       eng_zsk = load_key ( optarg, engine );
                        break;
                case 'K':
-                       printf("Not implemented yet\n");
-                       exit(EXIT_FAILURE);
+                       eng_ksk = load_key ( optarg, engine );
+                       /* I apologize for that, there is no API. */
+                       eng_ksk -> _extra.dnssec.flags |= LDNS_KEY_SEP_KEY;
                        break;
                case 'U':
                        signflags |= LDNS_SIGN_WITH_ALL_ALGORITHMS;
@@ -702,6 +845,33 @@ main(int argc, char *argv[])
                }
                argi++;
        }
+
+       /*
+       * 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 );
        
        if (ldns_key_list_key_count(keys) < 1) {
                fprintf(stderr, "Error: no keys to sign with. Aborting.\n\n");
@@ -803,17 +973,19 @@ main(int argc, char *argv[])
        ldns_rr_list_deep_free(added_rrs);
        
        LDNS_FREE(outputfile_name);
-       
-       CRYPTO_cleanup_all_ex_data();
+
+       shutdown_openssl ( engine );
 
        free(prog);
        exit(EXIT_SUCCESS);
 }
-#else
+
+#else /* !HAVE_SSL */
 int
-main(int argc, char **argv)
+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;
+       fprintf(stderr, "ldns-signzone needs OpenSSL support, which has not been compiled in\n");
+       return 1;
 }
 #endif /* HAVE_SSL */