swanctl: Add a --load-all command, performing --load-{creds,pools,conns}
[strongswan.git] / src / swanctl / commands / load_creds.c
1 /*
2 * Copyright (C) 2014 Martin Willi
3 * Copyright (C) 2014 revosec AG
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #define _GNU_SOURCE
17 #include <stdio.h>
18 #include <errno.h>
19 #include <unistd.h>
20 #include <sys/stat.h>
21
22 #include "command.h"
23 #include "swanctl.h"
24 #include "load_creds.h"
25
26 #include <credentials/sets/mem_cred.h>
27 #include <credentials/sets/callback_cred.h>
28
29 /**
30 * Load a single certificate over vici
31 */
32 static bool load_cert(vici_conn_t *conn, command_format_options_t format,
33 char *dir, char *type, chunk_t data)
34 {
35 vici_req_t *req;
36 vici_res_t *res;
37 bool ret = TRUE;
38
39 req = vici_begin("load-cert");
40
41 vici_add_key_valuef(req, "type", "%s", type);
42 vici_add_key_value(req, "data", data.ptr, data.len);
43
44 res = vici_submit(req, conn);
45 if (!res)
46 {
47 fprintf(stderr, "load-cert request failed: %s\n", strerror(errno));
48 return FALSE;
49 }
50 if (format & COMMAND_FORMAT_RAW)
51 {
52 vici_dump(res, "load-cert reply", format & COMMAND_FORMAT_PRETTY,
53 stdout);
54 }
55 else if (!streq(vici_find_str(res, "no", "success"), "yes"))
56 {
57 fprintf(stderr, "loading '%s' failed: %s\n",
58 dir, vici_find_str(res, "", "errmsg"));
59 ret = FALSE;
60 }
61 else
62 {
63 printf("loaded %s certificate '%s'\n", type, dir);
64 }
65 vici_free_res(res);
66 return ret;
67 }
68
69 /**
70 * Load certficiates from a directory
71 */
72 static void load_certs(vici_conn_t *conn, command_format_options_t format,
73 char *type, char *dir)
74 {
75 enumerator_t *enumerator;
76 struct stat st;
77 chunk_t *map;
78 char *path;
79
80 enumerator = enumerator_create_directory(dir);
81 if (enumerator)
82 {
83 while (enumerator->enumerate(enumerator, NULL, &path, &st))
84 {
85 if (S_ISREG(st.st_mode))
86 {
87 map = chunk_map(path, FALSE);
88 if (map)
89 {
90 load_cert(conn, format, path, type, *map);
91 chunk_unmap(map);
92 }
93 else
94 {
95 fprintf(stderr, "mapping '%s' failed: %s, skipped\n",
96 path, strerror(errno));
97 }
98 }
99 }
100 enumerator->destroy(enumerator);
101 }
102 }
103
104 /**
105 * Load a single private key over vici
106 */
107 static bool load_key(vici_conn_t *conn, command_format_options_t format,
108 char *dir, char *type, chunk_t data)
109 {
110 vici_req_t *req;
111 vici_res_t *res;
112 bool ret = TRUE;
113
114 req = vici_begin("load-key");
115
116 vici_add_key_valuef(req, "type", "%s", type);
117 vici_add_key_value(req, "data", data.ptr, data.len);
118
119 res = vici_submit(req, conn);
120 if (!res)
121 {
122 fprintf(stderr, "load-key request failed: %s\n", strerror(errno));
123 return FALSE;
124 }
125 if (format & COMMAND_FORMAT_RAW)
126 {
127 vici_dump(res, "load-key reply", format & COMMAND_FORMAT_PRETTY,
128 stdout);
129 }
130 else if (!streq(vici_find_str(res, "no", "success"), "yes"))
131 {
132 fprintf(stderr, "loading '%s' failed: %s\n",
133 dir, vici_find_str(res, "", "errmsg"));
134 ret = FALSE;
135 }
136 else
137 {
138 printf("loaded %s key '%s'\n", type, dir);
139 }
140 vici_free_res(res);
141 return ret;
142 }
143
144 /**
145 * Callback function to prompt for private key passwords
146 */
147 CALLBACK(password_cb, shared_key_t*,
148 char *prompt, shared_key_type_t type,
149 identification_t *me, identification_t *other,
150 id_match_t *match_me, id_match_t *match_other)
151 {
152 char *pwd = NULL;
153
154 if (type != SHARED_PRIVATE_KEY_PASS)
155 {
156 return NULL;
157 }
158 #ifdef HAVE_GETPASS
159 pwd = getpass(prompt);
160 #endif
161 if (!pwd || strlen(pwd) == 0)
162 {
163 return NULL;
164 }
165 if (match_me)
166 {
167 *match_me = ID_MATCH_PERFECT;
168 }
169 if (match_other)
170 {
171 *match_other = ID_MATCH_PERFECT;
172 }
173 return shared_key_create(type, chunk_clone(chunk_from_str(pwd)));
174 }
175
176 /**
177 * Try to parse a potentially encrypted private key using password prompt
178 */
179 static private_key_t* decrypt_key(char *name, char *type, chunk_t encoding)
180 {
181 key_type_t kt = KEY_ANY;
182 private_key_t *private;
183 callback_cred_t *cb;
184 char buf[128];
185
186 if (streq(type, "rsa"))
187 {
188 kt = KEY_RSA;
189 }
190 else if (streq(type, "ecdsa"))
191 {
192 kt = KEY_ECDSA;
193 }
194
195 snprintf(buf, sizeof(buf), "Password for '%s': ", name);
196
197 cb = callback_cred_create_shared(password_cb, buf);
198 lib->credmgr->add_set(lib->credmgr, &cb->set);
199
200 private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, kt,
201 BUILD_BLOB_PEM, encoding, BUILD_END);
202
203 lib->credmgr->remove_set(lib->credmgr, &cb->set);
204 cb->destroy(cb);
205
206 return private;
207 }
208
209 /**
210 * Try to parse a potentially encrypted private key using configured secret
211 */
212 static private_key_t* decrypt_key_with_config(settings_t *cfg, char *name,
213 char *type, chunk_t encoding)
214 { key_type_t kt = KEY_ANY;
215 enumerator_t *enumerator, *secrets;
216 char *section, *key, *value, *file, buf[128];
217 shared_key_t *shared;
218 private_key_t *private = NULL;
219 mem_cred_t *mem = NULL;
220
221 if (streq(type, "rsa"))
222 {
223 kt = KEY_RSA;
224 }
225 else if (streq(type, "ecdsa"))
226 {
227 kt = KEY_ECDSA;
228 }
229 else
230 {
231 type = "pkcs8";
232 }
233
234 /* load all secrets for this key type */
235 enumerator = cfg->create_section_enumerator(cfg, "secrets");
236 while (enumerator->enumerate(enumerator, &section))
237 {
238 if (strpfx(section, type))
239 {
240 file = cfg->get_str(cfg, "secrets.%s.file", NULL, section);
241 if (file && strcaseeq(file, name))
242 {
243 snprintf(buf, sizeof(buf), "secrets.%s", section);
244 secrets = cfg->create_key_value_enumerator(cfg, buf);
245 while (secrets->enumerate(secrets, &key, &value))
246 {
247 if (strpfx(key, "secret"))
248 {
249 if (!mem)
250 {
251 mem = mem_cred_create();
252 }
253 shared = shared_key_create(SHARED_PRIVATE_KEY_PASS,
254 chunk_clone(chunk_from_str(value)));
255 mem->add_shared(mem, shared, NULL);
256 }
257 }
258 secrets->destroy(secrets);
259 }
260 }
261 }
262 enumerator->destroy(enumerator);
263
264 if (mem)
265 {
266 lib->credmgr->add_local_set(lib->credmgr, &mem->set, FALSE);
267
268 private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, kt,
269 BUILD_BLOB_PEM, encoding, BUILD_END);
270
271 lib->credmgr->remove_local_set(lib->credmgr, &mem->set);
272
273 if (!private)
274 {
275 fprintf(stderr, "configured decryption secret for '%s' invalid\n",
276 name);
277 }
278
279 mem->destroy(mem);
280 }
281
282 return private;
283 }
284
285 /**
286 * Try to decrypt and load a private key
287 */
288 static bool load_encrypted_key(vici_conn_t *conn,
289 command_format_options_t format, settings_t *cfg,
290 char *rel, char *path, char *type, bool noprompt,
291 chunk_t data)
292 {
293 private_key_t *private;
294 bool loaded = FALSE;
295 chunk_t encoding;
296
297 private = decrypt_key_with_config(cfg, rel, type, data);
298 if (!private && !noprompt)
299 {
300 private = decrypt_key(rel, type, data);
301 }
302 if (private)
303 {
304 if (private->get_encoding(private, PRIVKEY_ASN1_DER, &encoding))
305 {
306 switch (private->get_type(private))
307 {
308 case KEY_RSA:
309 loaded = load_key(conn, format, path, "rsa", encoding);
310 break;
311 case KEY_ECDSA:
312 loaded = load_key(conn, format, path, "ecdsa", encoding);
313 break;
314 default:
315 break;
316 }
317 chunk_clear(&encoding);
318 }
319 private->destroy(private);
320 }
321 return loaded;
322 }
323
324 /**
325 * Load private keys from a directory
326 */
327 static void load_keys(vici_conn_t *conn, command_format_options_t format,
328 bool noprompt, settings_t *cfg, char *type, char *dir)
329 {
330 enumerator_t *enumerator;
331 struct stat st;
332 chunk_t *map;
333 char *path, *rel;
334
335 enumerator = enumerator_create_directory(dir);
336 if (enumerator)
337 {
338 while (enumerator->enumerate(enumerator, &rel, &path, &st))
339 {
340 if (S_ISREG(st.st_mode))
341 {
342 map = chunk_map(path, FALSE);
343 if (map)
344 {
345 if (!load_encrypted_key(conn, format, cfg, rel, path, type,
346 noprompt, *map))
347 {
348 load_key(conn, format, path, type, *map);
349 }
350 chunk_unmap(map);
351 }
352 else
353 {
354 fprintf(stderr, "mapping '%s' failed: %s, skipped\n",
355 path, strerror(errno));
356 }
357 }
358 }
359 enumerator->destroy(enumerator);
360 }
361 }
362
363 /**
364 * Load a single secret over VICI
365 */
366 static bool load_secret(vici_conn_t *conn, settings_t *cfg,
367 char *section, command_format_options_t format)
368 {
369 enumerator_t *enumerator;
370 vici_req_t *req;
371 vici_res_t *res;
372 chunk_t data;
373 char *key, *value, buf[128], *type = NULL;
374 bool ret = TRUE;
375 int i;
376 char *types[] = {
377 "eap",
378 "xauth",
379 "ike",
380 "rsa",
381 "ecdsa",
382 "pkcs8",
383 };
384
385 for (i = 0; i < countof(types); i++)
386 {
387 if (strpfx(section, types[i]))
388 {
389 type = types[i];
390 break;
391 }
392 }
393 if (!type)
394 {
395 fprintf(stderr, "ignoring unsupported secret '%s'\n", section);
396 return FALSE;
397 }
398 if (!streq(type, "eap") && !streq(type, "xauth") && !streq(type, "ike"))
399 { /* skip non-shared secrets */
400 return TRUE;
401 }
402
403 value = cfg->get_str(cfg, "secrets.%s.secret", NULL, section);
404 if (!value)
405 {
406 fprintf(stderr, "missing secret in '%s', ignored\n", section);
407 return FALSE;
408 }
409 if (strcasepfx(value, "0x"))
410 {
411 data = chunk_from_hex(chunk_from_str(value + 2), NULL);
412 }
413 else if (strcasepfx(value, "0s"))
414 {
415 data = chunk_from_base64(chunk_from_str(value + 2), NULL);
416 }
417 else
418 {
419 data = chunk_clone(chunk_from_str(value));
420 }
421
422 req = vici_begin("load-shared");
423
424 vici_add_key_valuef(req, "type", "%s", type);
425 vici_add_key_value(req, "data", data.ptr, data.len);
426 chunk_clear(&data);
427
428 vici_begin_list(req, "owners");
429 snprintf(buf, sizeof(buf), "secrets.%s", section);
430 enumerator = cfg->create_key_value_enumerator(cfg, buf);
431 while (enumerator->enumerate(enumerator, &key, &value))
432 {
433 if (strpfx(key, "id"))
434 {
435 vici_add_list_itemf(req, "%s", value);
436 }
437 }
438 enumerator->destroy(enumerator);
439 vici_end_list(req);
440
441 res = vici_submit(req, conn);
442 if (!res)
443 {
444 fprintf(stderr, "load-shared request failed: %s\n", strerror(errno));
445 return FALSE;
446 }
447 if (format & COMMAND_FORMAT_RAW)
448 {
449 vici_dump(res, "load-shared reply", format & COMMAND_FORMAT_PRETTY,
450 stdout);
451 }
452 else if (!streq(vici_find_str(res, "no", "success"), "yes"))
453 {
454 fprintf(stderr, "loading shared secret failed: %s\n",
455 vici_find_str(res, "", "errmsg"));
456 ret = FALSE;
457 }
458 else
459 {
460 printf("loaded %s secret '%s'\n", type, section);
461 }
462 vici_free_res(res);
463 return ret;
464 }
465
466 /**
467 * Clear all currently loaded credentials
468 */
469 static bool clear_creds(vici_conn_t *conn, command_format_options_t format)
470 {
471 vici_res_t *res;
472
473 res = vici_submit(vici_begin("clear-creds"), conn);
474 if (!res)
475 {
476 fprintf(stderr, "clear-creds request failed: %s\n", strerror(errno));
477 return FALSE;
478 }
479 if (format & COMMAND_FORMAT_RAW)
480 {
481 vici_dump(res, "clear-creds reply", format & COMMAND_FORMAT_PRETTY,
482 stdout);
483 }
484 vici_free_res(res);
485 return TRUE;
486 }
487
488 /**
489 * See header.
490 */
491 int load_creds_cfg(vici_conn_t *conn, command_format_options_t format,
492 settings_t *cfg, bool clear, bool noprompt)
493 {
494 enumerator_t *enumerator;
495 char *section;
496
497 if (clear)
498 {
499 if (!clear_creds(conn, format))
500 {
501 return ECONNREFUSED;
502 }
503 }
504
505 load_certs(conn, format, "x509", SWANCTL_X509DIR);
506 load_certs(conn, format, "x509ca", SWANCTL_X509CADIR);
507 load_certs(conn, format, "x509aa", SWANCTL_X509AADIR);
508 load_certs(conn, format, "x509crl", SWANCTL_X509CRLDIR);
509 load_certs(conn, format, "x509ac", SWANCTL_X509ACDIR);
510
511 load_keys(conn, format, noprompt, cfg, "rsa", SWANCTL_RSADIR);
512 load_keys(conn, format, noprompt, cfg, "ecdsa", SWANCTL_ECDSADIR);
513 load_keys(conn, format, noprompt, cfg, "any", SWANCTL_PKCS8DIR);
514
515 enumerator = cfg->create_section_enumerator(cfg, "secrets");
516 while (enumerator->enumerate(enumerator, &section))
517 {
518 load_secret(conn, cfg, section, format);
519 }
520 enumerator->destroy(enumerator);
521
522 return 0;
523 }
524
525 static int load_creds(vici_conn_t *conn)
526 {
527 bool clear = FALSE, noprompt = FALSE;
528 command_format_options_t format = COMMAND_FORMAT_NONE;
529 settings_t *cfg;
530 char *arg;
531 int ret;
532
533 while (TRUE)
534 {
535 switch (command_getopt(&arg))
536 {
537 case 'h':
538 return command_usage(NULL);
539 case 'c':
540 clear = TRUE;
541 continue;
542 case 'n':
543 noprompt = TRUE;
544 continue;
545 case 'P':
546 format |= COMMAND_FORMAT_PRETTY;
547 /* fall through to raw */
548 case 'r':
549 format |= COMMAND_FORMAT_RAW;
550 continue;
551 case EOF:
552 break;
553 default:
554 return command_usage("invalid --load-creds option");
555 }
556 break;
557 }
558
559 cfg = settings_create(SWANCTL_CONF);
560 if (!cfg)
561 {
562 fprintf(stderr, "parsing '%s' failed\n", SWANCTL_CONF);
563 return EINVAL;
564 }
565
566 ret = load_creds_cfg(conn, format, cfg, clear, noprompt);
567
568 cfg->destroy(cfg);
569
570 return ret;
571 }
572
573 /**
574 * Register the command.
575 */
576 static void __attribute__ ((constructor))reg()
577 {
578 command_register((command_t) {
579 load_creds, 's', "load-creds", "(re-)load credentials",
580 {"[--raw|--pretty]"},
581 {
582 {"help", 'h', 0, "show usage information"},
583 {"clear", 'c', 0, "clear previously loaded credentials"},
584 {"noprompt", 'n', 0, "do not prompt for passwords"},
585 {"raw", 'r', 0, "dump raw response message"},
586 {"pretty", 'P', 0, "dump raw response message in pretty print"},
587 }
588 });
589 }