1 /*
2  * Copyright (c) 2011 Surfnet
3  * Copyright (c) 2011 .SE (The Internet Infrastructure Foundation).
4  * Copyright (c) 2011 OpenDNSSEC AB (svb)
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
22  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
24  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
26  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  */
29 
30 #include "config.h"
31 #include <getopt.h>
32 
33 #include "cmdhandler.h"
34 #include "daemon/enforcercommands.h"
35 #include "daemon/engine.h"
36 #include "file.h"
37 #include "log.h"
38 #include "str.h"
39 #include "clientpipe.h"
40 #include "duration.h"
41 #include "libhsm.h"
42 #include "libhsmdns.h"
43 #include "db/key_data.h"
44 #include "db/db_error.h"
45 
46 #include "keystate/keystate_export_cmd.h"
47 #include "keystate/keystate_list_cmd.h"
48 
49 static const char *module_str = "keystate_export_cmd";
50 
51 /** Retrieve KEY from HSM, should only be called for DNSKEYs
52  * @param id, locator of DNSKEY on HSM
53  * @param zone, name of zone key belongs to
54  * @param algorithm, alg of DNSKEY
55  * @param ttl, ttl DS should get. if 0 DNSKEY_TTL is used.
56  * @return RR on succes, NULL on error */
57 static ldns_rr *
get_dnskey(const char * id,const char * zone,const char * keytype,int alg,uint32_t ttl)58 get_dnskey(const char *id, const char *zone, const char *keytype, int alg, uint32_t ttl)
59 {
60     libhsm_key_t *key;
61     hsm_sign_params_t *sign_params;
62     ldns_rr *dnskey_rr;
63     /* Code to output the DNSKEY record  (stolen from hsmutil) */
64     hsm_ctx_t *hsm_ctx = hsm_create_context();
65     if (!hsm_ctx) {
66         ods_log_error("[%s] Could not connect to HSM", module_str);
67         return NULL;
68     }
69     if (!(key = hsm_find_key_by_id(hsm_ctx, id))) {
70         hsm_destroy_context(hsm_ctx);
71         return NULL;
72     }
73 
74     /* Sign params only need to be kept around
75      * for the hsm_get_dnskey() call. */
76     sign_params = hsm_sign_params_new();
77     sign_params->owner = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, zone);
78     sign_params->algorithm = (ldns_algorithm) alg;
79     sign_params->flags = LDNS_KEY_ZONE_KEY;
80 
81     if (keytype && (!strcasecmp(keytype, "KSK") || !strcasecmp(keytype, "CSK")))
82         sign_params->flags = sign_params->flags | LDNS_KEY_SEP_KEY;
83 
84     /* Get the DNSKEY record */
85     dnskey_rr = hsm_get_dnskey(hsm_ctx, key, sign_params);
86 
87     libhsm_key_free(key);
88     hsm_sign_params_free(sign_params);
89     hsm_destroy_context(hsm_ctx);
90 
91     /* Override the TTL in the dnskey rr */
92     if (ttl)
93         ldns_rr_set_ttl(dnskey_rr, ttl);
94 
95     return dnskey_rr;
96 }
97 
98 /**
99  * Print DNSKEY record or SHA1 and SHA256 DS records, should only be
100  * called for DNSKEYs.
101  *
102  * @param sockfd, Where to print to
103  * @param key, Key to be printed. Must not be NULL.
104  * @param zone, name of zone key belongs to. Must not be NULL.
105  * @param bind_style, bool. print DS rather than DNSKEY rr.
106  * @return 1 on succes 0 on error
107  */
108 static int
print_ds_from_id(int sockfd,key_data_t * key,const char * zone,const char * state,int bind_style,int print_sha1)109 print_ds_from_id(int sockfd, key_data_t *key, const char *zone,
110 	const char* state, int bind_style, int print_sha1)
111 {
112     ldns_rr *dnskey_rr;
113     ldns_rr *ds_sha_rr;
114     int ttl = 0;
115     const char *locator;
116     char *rrstr;
117 
118     assert(key);
119     assert(zone);
120 
121     locator = hsm_key_locator(key_data_hsm_key(key));
122     if (!locator)
123         return 1;
124     /* This fetches the states from the DB, I'm only assuming they get
125      * cleaned up when 'key' is cleaned(?) */
126     if (key_data_cache_key_states(key) != DB_OK)
127         return 1;
128 
129     ttl = key_state_ttl(key_data_cached_dnskey(key));
130 
131     dnskey_rr = get_dnskey(locator, zone, key_data_role_text(key), key_data_algorithm(key), ttl);
132     if (!dnskey_rr)
133         return 1;
134 
135     if (bind_style) {
136         ldns_rr_set_ttl(dnskey_rr, key_state_ttl (key_data_cached_ds(key)));
137         if (print_sha1) {
138             ds_sha_rr = ldns_key_rr2ds(dnskey_rr, LDNS_SHA1);
139             rrstr = ldns_rr2str(ds_sha_rr);
140             ldns_rr_free(ds_sha_rr);
141             /* TODO log error on failure */
142             (void)client_printf(sockfd, ";%s %s DS record (SHA1):\n%s", state, key_data_role_text(key), rrstr);
143             LDNS_FREE(rrstr);
144         } else {
145             ds_sha_rr = ldns_key_rr2ds(dnskey_rr, LDNS_SHA256);
146             rrstr = ldns_rr2str(ds_sha_rr);
147             ldns_rr_free(ds_sha_rr);
148             /* TODO log error on failure */
149             (void)client_printf(sockfd, ";%s %s DS record (SHA256):\n%s", state, key_data_role_text(key), rrstr);
150             LDNS_FREE(rrstr);
151         }
152     } else {
153         rrstr = ldns_rr2str_fmt(ldns_output_format_nocomments, dnskey_rr);
154         /* TODO log error on failure */
155         (void)client_printf(sockfd, "%s", rrstr);
156         LDNS_FREE(rrstr);
157     }
158 
159     ldns_rr_free(dnskey_rr);
160     return 0;
161 }
162 
163 static int
perform_keystate_export(int sockfd,db_connection_t * dbconn,const char * zonename,const char * keytype,const char * keystate,const hsm_key_t * hsmkey,int all,int bind_style,int print_sha1)164 perform_keystate_export(int sockfd, db_connection_t *dbconn,
165 	const char *zonename, const char *keytype, const char *keystate,
166         const hsm_key_t *hsmkey, int all, int bind_style, int print_sha1)
167 {
168     key_data_list_t *key_list = NULL;
169     key_data_t *key;
170     zone_db_t *zone = NULL;
171     db_clause_list_t* clause_list = NULL;
172     const char *azonename = NULL;
173 
174     /* Find all keys related to zonename */
175     if (all == 0) {
176         if (!(key_list = key_data_list_new(dbconn)) ||
177               !(clause_list = db_clause_list_new()) ||
178               !(zone = zone_db_new_get_by_name(dbconn, zonename)) ||
179               !key_data_zone_id_clause(clause_list, zone_db_id(zone)) ||
180               (hsmkey && !key_data_hsm_key_id_clause(clause_list, hsm_key_id(hsmkey))) ||
181               key_data_list_get_by_clauses(key_list, clause_list))
182         {
183             key_data_list_free(key_list);
184             db_clause_list_free(clause_list);
185             zone_db_free(zone);
186             ods_log_error("[%s] Error fetching from database", module_str);
187             return 1;
188         }
189         db_clause_list_free(clause_list);
190         zone_db_free(zone);
191     } else {
192         if (!(key_list = key_data_list_new_get(dbconn)) ||
193                 !(clause_list = db_clause_list_new()) ||
194                 (hsmkey && !key_data_hsm_key_id_clause(clause_list, hsm_key_id(hsmkey))) ||
195                 key_data_list_get_by_clauses(key_list, clause_list))
196         {
197             key_data_list_free(key_list);
198             db_clause_list_free(clause_list);
199             ods_log_error("[%s] Error fetching from database", module_str);
200             return 1;
201         }
202         db_clause_list_free(clause_list);
203     }
204 
205     /* Print data*/
206     while ((key = key_data_list_get_next(key_list))) {
207         if (keytype && strcasecmp(key_data_role_text(key), keytype)) {
208             key_data_free(key);
209             continue;
210         }
211         if (keystate && strcasecmp(map_keystate(key), keystate)) {
212             key_data_free(key);
213             continue;
214         }
215         if (!keytype && !keystate && !hsmkey &&
216               key_data_ds_at_parent(key) != KEY_DATA_DS_AT_PARENT_SUBMIT &&
217               key_data_ds_at_parent(key) != KEY_DATA_DS_AT_PARENT_SUBMITTED &&
218               key_data_ds_at_parent(key) != KEY_DATA_DS_AT_PARENT_RETRACT   &&
219               key_data_ds_at_parent(key) != KEY_DATA_DS_AT_PARENT_RETRACTED)
220         {
221             key_data_free(key);
222             continue;
223         }
224 
225         if (all && (!(zone = zone_db_new (dbconn)) || (zone_db_get_by_id(zone, key_data_zone_id(key))) || !(azonename = zone_db_name(zone)))) {
226             ods_log_error("[%s] Error fetching from database", module_str);
227             client_printf_err(sockfd, "Error fetching from database \n");
228         }
229 
230         /* check return code TODO */
231         if (key_data_cache_hsm_key(key) == DB_OK) {
232             if (print_ds_from_id(sockfd, key, (const char*)azonename?azonename:zonename, (const char*)map_keystate(key), bind_style, print_sha1)) {
233                 ods_log_error("[%s] Error in print_ds_from_id", module_str);
234                 client_printf_err(sockfd, "Error in print_ds_from_id \n");
235             }
236         } else {
237             ods_log_error("[%s] Error fetching from database", module_str);
238             client_printf_err(sockfd, "Error fetching from database \n");
239         }
240         key_data_free(key);
241 
242         if (all)
243             zone_db_free(zone);
244     }
245     key_data_list_free(key_list);
246     return 0;
247 }
248 
249 static void
usage(int sockfd)250 usage(int sockfd)
251 {
252     client_printf(sockfd,
253          "key export\n"
254          "	--zone <zone> | --all			aka -z | -a \n"
255          "	--keystate <state>			aka -e\n"
256          "	--keytype <type>			aka -t \n"
257          "	--cka_id <CKA_ID>			aka -k \n"
258          "	[--ds [--sha1]]				aka -d [-s]\n"
259     );
260 }
261 
262 static void
help(int sockfd)263 help(int sockfd)
264 {
265     client_printf(sockfd,
266          "Export DNSKEY(s) for a given zone or all of them from the database.\n"
267          "If keytype and keystate are not specified, KSKs which are waiting for command ds-submit, ds-seen, ds-retract and ds-gone are shown. Otherwise both keystate and keytype must be given.\n"
268          "If cka_id is specified then that key is output for the specified zones.\n"
269 
270          "\nOptions:\n"
271          "zone|all	specify a zone or all of them\n"
272          "keystate	limit the output to a given state\n"
273          "keytype		limit the output to a given type, can be ZSK, KSK, or CSK\n"
274          "cka_id		limit the output to the given key locator\n"
275          "ds		export DS in BIND format which can be used for upload to a registry\n"
276          "sha1		When outputting DS print sha1 instead of sha256\n");
277 }
278 
279 static int
run(int sockfd,cmdhandler_ctx_type * context,const char * cmd)280 run(int sockfd, cmdhandler_ctx_type* context, const char *cmd)
281 {
282     #define NARGV 11
283     char buf[ODS_SE_MAXLINE];
284     const char *argv[NARGV];
285     int argc = 0;
286     const char *zonename = NULL;
287     const char* keytype = NULL;
288     const char* keystate = NULL;
289     const char* cka_id = NULL;
290     zone_db_t * zone = NULL;
291     hsm_key_t *hsmkey = NULL;
292     int all = 0;
293     int ds = 0;
294     int bsha1 = 0;
295     int long_index = 0, opt = 0;
296     db_connection_t* dbconn = getconnectioncontext(context);
297 
298     static struct option long_options[] = {
299         {"zone", required_argument, 0, 'z'},
300         {"keytype", required_argument, 0, 't'},
301         {"keystate", required_argument, 0, 'e'},
302         {"cka_id", required_argument, 0, 'k'},
303         {"all", no_argument, 0, 'a'},
304         {"ds", no_argument, 0, 'd'},
305         {"sha1", no_argument, 0, 's'},
306         {0, 0, 0, 0}
307     };
308 
309     ods_log_debug("[%s] %s command", module_str, key_export_funcblock.cmdname);
310 
311     /* Use buf as an intermediate buffer for the command.*/
312     strncpy(buf, cmd, sizeof(buf));
313     buf[sizeof(buf)-1] = '\0';
314 
315     /* separate the arguments*/
316     argc = ods_str_explode(buf, NARGV, argv);
317     if (argc == -1) {
318         client_printf_err(sockfd, "too many arguments\n");
319         ods_log_error("[%s] too many arguments for %s command",
320                       module_str, key_export_funcblock.cmdname);
321         return -1;
322     }
323 
324     optind = 0;
325     while ((opt = getopt_long(argc, (char* const*)argv, "z:t:e:k:ads", long_options, &long_index)) != -1) {
326         switch (opt) {
327             case 'z':
328                 zonename = optarg;
329                 break;
330             case 't':
331                 keytype = optarg;
332                 break;
333             case 'e':
334                 keystate = optarg;
335                 break;
336             case 'k':
337                 cka_id = optarg;
338                 break;
339             case 'a':
340                 all = 1;
341                 break;
342             case 'd':
343                 ds = 1;
344                 break;
345             case 's':
346                 bsha1 = 1;
347                 break;
348             default:
349                 client_printf_err(sockfd, "unknown arguments\n");
350                 ods_log_error("[%s] unknown arguments for %s command",
351                                 module_str, key_export_funcblock.cmdname);
352                 return -1;
353         }
354     }
355 
356     if (keytype) {
357         if (strcasecmp(keytype, "KSK") && strcasecmp(keytype, "ZSK") && strcasecmp(keytype, "CSK")) {
358             ods_log_error("[%s] unknown keytype, should be one of KSK, ZSK, or CSK", module_str);
359             client_printf_err(sockfd, "unknown keytype, should be one of KSK, ZSK, or CSK\n");
360             return -1;
361         }
362     }
363 
364     if (keystate) {
365         if (strcasecmp(keystate, "generate") && strcasecmp(keystate, "publish") && strcasecmp(keystate, "ready") && strcasecmp(keystate, "active") && strcasecmp(keystate, "retire") && strcasecmp(keystate, "unknown") && strcasecmp(keystate, "mixed")) {
366             ods_log_error("[%s] unknown keystate", module_str);
367             client_printf_err(sockfd, "unknown keystate\n");
368             return -1;
369         }
370     }
371 
372 
373     if ((!zonename && !all) || (zonename && all)) {
374         ods_log_error("[%s] expected either --zone or --all for %s command", module_str, key_export_funcblock.cmdname);
375         client_printf_err(sockfd, "expected either --zone or --all \n");
376         return -1;
377     }
378     if (zonename && !(zone = zone_db_new_get_by_name(dbconn, zonename))) {
379         ods_log_error("[%s] Unknown zone: %s", module_str, zonename);
380         client_printf_err(sockfd, "Unknown zone: %s\n", zonename);
381         return -1;
382     }
383     free(zone);
384     zone = NULL;
385 
386     /* if no keystate and keytype are given, default values are used.
387      * Default type is KSK, default states are waiting for ds-submit, ds-seen, ds-retract and ds-gone.
388      * Otherwise both keystate and keytype must be specified.
389      */
390     if ((keytype && !keystate) || (!keytype && keystate)) {
391         ods_log_error("[%s] expected both --keystate and --keytype together or none of them", module_str);
392         client_printf_err(sockfd, "expected both --keystate and --keytype together or none of them\n");
393         return -1;
394     }
395 
396     if (cka_id && !(hsmkey = hsm_key_new_get_by_locator(dbconn, cka_id))) {
397         client_printf_err(sockfd, "CKA_ID %s can not be found!\n", cka_id);
398         return -1;
399     }
400 
401     /* perform task immediately */
402     return perform_keystate_export(sockfd, dbconn, zonename, (const char*) keytype, (const char*) keystate, hsmkey, all, ds, bsha1);
403 }
404 
405 struct cmd_func_block key_export_funcblock = {
406     "key export", &usage, &help, NULL, &run
407 };
408