d2ebc22eb4ccce2d9fd2363560941c9114a624d4
[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 #include <credentials/containers/pkcs12.h>
29
30 /**
31 * Load a single certificate over vici
32 */
33 static bool load_cert(vici_conn_t *conn, command_format_options_t format,
34 char *dir, char *type, chunk_t data)
35 {
36 vici_req_t *req;
37 vici_res_t *res;
38 bool ret = TRUE;
39
40 req = vici_begin("load-cert");
41
42 vici_add_key_valuef(req, "type", "%s", type);
43 vici_add_key_value(req, "data", data.ptr, data.len);
44
45 res = vici_submit(req, conn);
46 if (!res)
47 {
48 fprintf(stderr, "load-cert request failed: %s\n", strerror(errno));
49 return FALSE;
50 }
51 if (format & COMMAND_FORMAT_RAW)
52 {
53 vici_dump(res, "load-cert reply", format & COMMAND_FORMAT_PRETTY,
54 stdout);
55 }
56 else if (!streq(vici_find_str(res, "no", "success"), "yes"))
57 {
58 fprintf(stderr, "loading '%s' failed: %s\n",
59 dir, vici_find_str(res, "", "errmsg"));
60 ret = FALSE;
61 }
62 else
63 {
64 printf("loaded %s certificate from '%s'\n", type, dir);
65 }
66 vici_free_res(res);
67 return ret;
68 }
69
70 /**
71 * Load certficiates from a directory
72 */
73 static void load_certs(vici_conn_t *conn, command_format_options_t format,
74 char *type, char *dir)
75 {
76 enumerator_t *enumerator;
77 struct stat st;
78 chunk_t *map;
79 char *path;
80
81 enumerator = enumerator_create_directory(dir);
82 if (enumerator)
83 {
84 while (enumerator->enumerate(enumerator, NULL, &path, &st))
85 {
86 if (S_ISREG(st.st_mode))
87 {
88 map = chunk_map(path, FALSE);
89 if (map)
90 {
91 load_cert(conn, format, path, type, *map);
92 chunk_unmap(map);
93 }
94 else
95 {
96 fprintf(stderr, "mapping '%s' failed: %s, skipped\n",
97 path, strerror(errno));
98 }
99 }
100 }
101 enumerator->destroy(enumerator);
102 }
103 }
104
105 /**
106 * Load a single private key over vici
107 */
108 static bool load_key(vici_conn_t *conn, command_format_options_t format,
109 char *dir, char *type, chunk_t data)
110 {
111 vici_req_t *req;
112 vici_res_t *res;
113 bool ret = TRUE;
114
115 req = vici_begin("load-key");
116
117 if (streq(type, "pkcs8"))
118 { /* as used by vici */
119 vici_add_key_valuef(req, "type", "any");
120 }
121 else
122 {
123 vici_add_key_valuef(req, "type", "%s", type);
124 }
125 vici_add_key_value(req, "data", data.ptr, data.len);
126
127 res = vici_submit(req, conn);
128 if (!res)
129 {
130 fprintf(stderr, "load-key request failed: %s\n", strerror(errno));
131 return FALSE;
132 }
133 if (format & COMMAND_FORMAT_RAW)
134 {
135 vici_dump(res, "load-key reply", format & COMMAND_FORMAT_PRETTY,
136 stdout);
137 }
138 else if (!streq(vici_find_str(res, "no", "success"), "yes"))
139 {
140 fprintf(stderr, "loading '%s' failed: %s\n",
141 dir, vici_find_str(res, "", "errmsg"));
142 ret = FALSE;
143 }
144 else
145 {
146 printf("loaded %s key from '%s'\n", type, dir);
147 }
148 vici_free_res(res);
149 return ret;
150 }
151
152 /**
153 * Load a private key of any type to vici
154 */
155 static bool load_key_anytype(vici_conn_t *conn, command_format_options_t format,
156 char *path, private_key_t *private)
157 {
158 bool loaded = FALSE;
159 chunk_t encoding;
160
161 if (!private->get_encoding(private, PRIVKEY_ASN1_DER, &encoding))
162 {
163 fprintf(stderr, "encoding private key from '%s' failed\n", path);
164 return FALSE;
165 }
166 switch (private->get_type(private))
167 {
168 case KEY_RSA:
169 loaded = load_key(conn, format, path, "rsa", encoding);
170 break;
171 case KEY_ECDSA:
172 loaded = load_key(conn, format, path, "ecdsa", encoding);
173 break;
174 default:
175 fprintf(stderr, "unsupported key type in '%s'\n", path);
176 break;
177 }
178 chunk_clear(&encoding);
179 return loaded;
180 }
181
182 /**
183 * Data passed to password callback
184 */
185 typedef struct {
186 char prompt[128];
187 mem_cred_t *cache;
188 } cb_data_t;
189
190 /**
191 * Callback function to prompt for private key passwords
192 */
193 CALLBACK(password_cb, shared_key_t*,
194 cb_data_t *data, shared_key_type_t type,
195 identification_t *me, identification_t *other,
196 id_match_t *match_me, id_match_t *match_other)
197 {
198 shared_key_t *shared;
199 char *pwd = NULL;
200
201 if (type != SHARED_PRIVATE_KEY_PASS)
202 {
203 return NULL;
204 }
205 #ifdef HAVE_GETPASS
206 pwd = getpass(data->prompt);
207 #endif
208 if (!pwd || strlen(pwd) == 0)
209 {
210 return NULL;
211 }
212 if (match_me)
213 {
214 *match_me = ID_MATCH_PERFECT;
215 }
216 if (match_other)
217 {
218 *match_other = ID_MATCH_PERFECT;
219 }
220 shared = shared_key_create(type, chunk_clone(chunk_from_str(pwd)));
221 /* cache secret if it is required more than once (PKCS#12) */
222 data->cache->add_shared(data->cache, shared, NULL);
223 return shared->get_ref(shared);
224 }
225
226 /**
227 * Determine credential type and subtype from a type string
228 */
229 static bool determine_credtype(char *type, credential_type_t *credtype,
230 int *subtype)
231 {
232 struct {
233 char *type;
234 credential_type_t credtype;
235 int subtype;
236 } map[] = {
237 { "pkcs8", CRED_PRIVATE_KEY, KEY_ANY, },
238 { "rsa", CRED_PRIVATE_KEY, KEY_RSA, },
239 { "ecdsa", CRED_PRIVATE_KEY, KEY_ECDSA, },
240 { "pkcs12", CRED_CONTAINER, CONTAINER_PKCS12, },
241 };
242 int i;
243
244 for (i = 0; i < countof(map); i++)
245 {
246 if (streq(map[i].type, type))
247 {
248 *credtype = map[i].credtype;
249 *subtype = map[i].subtype;
250 return TRUE;
251 }
252 }
253 return FALSE;
254 }
255
256 /**
257 * Try to parse a potentially encrypted credential using password prompt
258 */
259 static void* decrypt(char *name, char *type, chunk_t encoding)
260 {
261 credential_type_t credtype;
262 int subtype;
263 void *cred;
264 callback_cred_t *cb;
265 cb_data_t data;
266
267 if (!determine_credtype(type, &credtype, &subtype))
268 {
269 return NULL;
270 }
271
272 snprintf(data.prompt, sizeof(data.prompt), "Password for %s file '%s': ",
273 type, name);
274
275 data.cache = mem_cred_create();
276 lib->credmgr->add_set(lib->credmgr, &data.cache->set);
277 cb = callback_cred_create_shared(password_cb, &data);
278 lib->credmgr->add_set(lib->credmgr, &cb->set);
279
280 cred = lib->creds->create(lib->creds, credtype, subtype,
281 BUILD_BLOB_PEM, encoding, BUILD_END);
282
283 lib->credmgr->remove_set(lib->credmgr, &data.cache->set);
284 data.cache->destroy(data.cache);
285 lib->credmgr->remove_set(lib->credmgr, &cb->set);
286 cb->destroy(cb);
287
288 return cred;
289 }
290
291 /**
292 * Try to parse a potentially encrypted credential using configured secret
293 */
294 static void* decrypt_with_config(settings_t *cfg, char *name, char *type,
295 chunk_t encoding)
296 {
297 credential_type_t credtype;
298 int subtype;
299 enumerator_t *enumerator, *secrets;
300 char *section, *key, *value, *file, buf[128];
301 shared_key_t *shared;
302 void *cred = NULL;
303 mem_cred_t *mem = NULL;
304
305 if (!determine_credtype(type, &credtype, &subtype))
306 {
307 return NULL;
308 }
309
310 /* load all secrets for this key type */
311 enumerator = cfg->create_section_enumerator(cfg, "secrets");
312 while (enumerator->enumerate(enumerator, &section))
313 {
314 if (strpfx(section, type))
315 {
316 file = cfg->get_str(cfg, "secrets.%s.file", NULL, section);
317 if (file && strcaseeq(file, name))
318 {
319 snprintf(buf, sizeof(buf), "secrets.%s", section);
320 secrets = cfg->create_key_value_enumerator(cfg, buf);
321 while (secrets->enumerate(secrets, &key, &value))
322 {
323 if (strpfx(key, "secret"))
324 {
325 if (!mem)
326 {
327 mem = mem_cred_create();
328 }
329 shared = shared_key_create(SHARED_PRIVATE_KEY_PASS,
330 chunk_clone(chunk_from_str(value)));
331 mem->add_shared(mem, shared, NULL);
332 }
333 }
334 secrets->destroy(secrets);
335 }
336 }
337 }
338 enumerator->destroy(enumerator);
339
340 if (mem)
341 {
342 lib->credmgr->add_local_set(lib->credmgr, &mem->set, FALSE);
343
344 cred = lib->creds->create(lib->creds, credtype, subtype,
345 BUILD_BLOB_PEM, encoding, BUILD_END);
346
347 lib->credmgr->remove_local_set(lib->credmgr, &mem->set);
348
349 if (!cred)
350 {
351 fprintf(stderr, "configured decryption secret for '%s' invalid\n",
352 name);
353 }
354
355 mem->destroy(mem);
356 }
357
358 return cred;
359 }
360
361 /**
362 * Try to decrypt and load a private key
363 */
364 static bool load_encrypted_key(vici_conn_t *conn,
365 command_format_options_t format, settings_t *cfg,
366 char *rel, char *path, char *type, bool noprompt,
367 chunk_t data)
368 {
369 private_key_t *private;
370 bool loaded = FALSE;
371
372 private = decrypt_with_config(cfg, rel, type, data);
373 if (!private && !noprompt)
374 {
375 private = decrypt(rel, type, data);
376 }
377 if (private)
378 {
379 loaded = load_key_anytype(conn, format, path, private);
380 private->destroy(private);
381 }
382 return loaded;
383 }
384
385 /**
386 * Load private keys from a directory
387 */
388 static void load_keys(vici_conn_t *conn, command_format_options_t format,
389 bool noprompt, settings_t *cfg, char *type, char *dir)
390 {
391 enumerator_t *enumerator;
392 struct stat st;
393 chunk_t *map;
394 char *path, *rel;
395
396 enumerator = enumerator_create_directory(dir);
397 if (enumerator)
398 {
399 while (enumerator->enumerate(enumerator, &rel, &path, &st))
400 {
401 if (S_ISREG(st.st_mode))
402 {
403 map = chunk_map(path, FALSE);
404 if (map)
405 {
406 if (!load_encrypted_key(conn, format, cfg, rel, path, type,
407 noprompt, *map))
408 {
409 load_key(conn, format, path, type, *map);
410 }
411 chunk_unmap(map);
412 }
413 else
414 {
415 fprintf(stderr, "mapping '%s' failed: %s, skipped\n",
416 path, strerror(errno));
417 }
418 }
419 }
420 enumerator->destroy(enumerator);
421 }
422 }
423
424 /**
425 * Load credentials from a PKCS#12 container over vici
426 */
427 static bool load_pkcs12(vici_conn_t *conn, command_format_options_t format,
428 char *path, pkcs12_t *p12)
429 {
430 enumerator_t *enumerator;
431 certificate_t *cert;
432 private_key_t *private;
433 chunk_t encoding;
434 bool loaded = TRUE;
435
436 enumerator = p12->create_cert_enumerator(p12);
437 while (loaded && enumerator->enumerate(enumerator, &cert))
438 {
439 loaded = FALSE;
440 if (cert->get_encoding(cert, CERT_ASN1_DER, &encoding))
441 {
442 loaded = load_cert(conn, format, path, "x509", encoding);
443 if (loaded)
444 {
445 fprintf(stderr, " %Y\n", cert->get_subject(cert));
446 }
447 free(encoding.ptr);
448 }
449 else
450 {
451 fprintf(stderr, "encoding certificate from '%s' failed\n", path);
452 }
453 }
454 enumerator->destroy(enumerator);
455
456 enumerator = p12->create_key_enumerator(p12);
457 while (loaded && enumerator->enumerate(enumerator, &private))
458 {
459 loaded = load_key_anytype(conn, format, path, private);
460 }
461 enumerator->destroy(enumerator);
462
463 return loaded;
464 }
465
466 /**
467 * Try to decrypt and load credentials from a container
468 */
469 static bool load_encrypted_container(vici_conn_t *conn,
470 command_format_options_t format, settings_t *cfg, char *rel,
471 char *path, char *type, bool noprompt, chunk_t data)
472 {
473 container_t *container;
474 bool loaded = FALSE;
475
476 container = decrypt_with_config(cfg, rel, type, data);
477 if (!container && !noprompt)
478 {
479 container = decrypt(rel, type, data);
480 }
481 if (container)
482 {
483 switch (container->get_type(container))
484 {
485 case CONTAINER_PKCS12:
486 loaded = load_pkcs12(conn, format, path, (pkcs12_t*)container);
487 break;
488 default:
489 break;
490 }
491 container->destroy(container);
492 }
493 return loaded;
494 }
495
496 /**
497 * Load credential containers from a directory
498 */
499 static void load_containers(vici_conn_t *conn, command_format_options_t format,
500 bool noprompt, settings_t *cfg, char *type, char *dir)
501 {
502 enumerator_t *enumerator;
503 struct stat st;
504 chunk_t *map;
505 char *path, *rel;
506
507 enumerator = enumerator_create_directory(dir);
508 if (enumerator)
509 {
510 while (enumerator->enumerate(enumerator, &rel, &path, &st))
511 {
512 if (S_ISREG(st.st_mode))
513 {
514 map = chunk_map(path, FALSE);
515 if (map)
516 {
517 load_encrypted_container(conn, format, cfg, rel, path,
518 type, noprompt, *map);
519 chunk_unmap(map);
520 }
521 else
522 {
523 fprintf(stderr, "mapping '%s' failed: %s, skipped\n",
524 path, strerror(errno));
525 }
526 }
527 }
528 enumerator->destroy(enumerator);
529 }
530 }
531
532 /**
533 * Load a single secret over VICI
534 */
535 static bool load_secret(vici_conn_t *conn, settings_t *cfg,
536 char *section, command_format_options_t format)
537 {
538 enumerator_t *enumerator;
539 vici_req_t *req;
540 vici_res_t *res;
541 chunk_t data;
542 char *key, *value, buf[128], *type = NULL;
543 bool ret = TRUE;
544 int i;
545 char *types[] = {
546 "eap",
547 "xauth",
548 "ike",
549 "rsa",
550 "ecdsa",
551 "pkcs8",
552 "pkcs12",
553 };
554
555 for (i = 0; i < countof(types); i++)
556 {
557 if (strpfx(section, types[i]))
558 {
559 type = types[i];
560 break;
561 }
562 }
563 if (!type)
564 {
565 fprintf(stderr, "ignoring unsupported secret '%s'\n", section);
566 return FALSE;
567 }
568 if (!streq(type, "eap") && !streq(type, "xauth") && !streq(type, "ike"))
569 { /* skip non-shared secrets */
570 return TRUE;
571 }
572
573 value = cfg->get_str(cfg, "secrets.%s.secret", NULL, section);
574 if (!value)
575 {
576 fprintf(stderr, "missing secret in '%s', ignored\n", section);
577 return FALSE;
578 }
579 if (strcasepfx(value, "0x"))
580 {
581 data = chunk_from_hex(chunk_from_str(value + 2), NULL);
582 }
583 else if (strcasepfx(value, "0s"))
584 {
585 data = chunk_from_base64(chunk_from_str(value + 2), NULL);
586 }
587 else
588 {
589 data = chunk_clone(chunk_from_str(value));
590 }
591
592 req = vici_begin("load-shared");
593
594 vici_add_key_valuef(req, "type", "%s", type);
595 vici_add_key_value(req, "data", data.ptr, data.len);
596 chunk_clear(&data);
597
598 vici_begin_list(req, "owners");
599 snprintf(buf, sizeof(buf), "secrets.%s", section);
600 enumerator = cfg->create_key_value_enumerator(cfg, buf);
601 while (enumerator->enumerate(enumerator, &key, &value))
602 {
603 if (strpfx(key, "id"))
604 {
605 vici_add_list_itemf(req, "%s", value);
606 }
607 }
608 enumerator->destroy(enumerator);
609 vici_end_list(req);
610
611 res = vici_submit(req, conn);
612 if (!res)
613 {
614 fprintf(stderr, "load-shared request failed: %s\n", strerror(errno));
615 return FALSE;
616 }
617 if (format & COMMAND_FORMAT_RAW)
618 {
619 vici_dump(res, "load-shared reply", format & COMMAND_FORMAT_PRETTY,
620 stdout);
621 }
622 else if (!streq(vici_find_str(res, "no", "success"), "yes"))
623 {
624 fprintf(stderr, "loading shared secret failed: %s\n",
625 vici_find_str(res, "", "errmsg"));
626 ret = FALSE;
627 }
628 else
629 {
630 printf("loaded %s secret '%s'\n", type, section);
631 }
632 vici_free_res(res);
633 return ret;
634 }
635
636 /**
637 * Clear all currently loaded credentials
638 */
639 static bool clear_creds(vici_conn_t *conn, command_format_options_t format)
640 {
641 vici_res_t *res;
642
643 res = vici_submit(vici_begin("clear-creds"), conn);
644 if (!res)
645 {
646 fprintf(stderr, "clear-creds request failed: %s\n", strerror(errno));
647 return FALSE;
648 }
649 if (format & COMMAND_FORMAT_RAW)
650 {
651 vici_dump(res, "clear-creds reply", format & COMMAND_FORMAT_PRETTY,
652 stdout);
653 }
654 vici_free_res(res);
655 return TRUE;
656 }
657
658 /**
659 * See header.
660 */
661 int load_creds_cfg(vici_conn_t *conn, command_format_options_t format,
662 settings_t *cfg, bool clear, bool noprompt)
663 {
664 enumerator_t *enumerator;
665 char *section;
666
667 if (clear)
668 {
669 if (!clear_creds(conn, format))
670 {
671 return ECONNREFUSED;
672 }
673 }
674
675 load_certs(conn, format, "x509", SWANCTL_X509DIR);
676 load_certs(conn, format, "x509ca", SWANCTL_X509CADIR);
677 load_certs(conn, format, "x509aa", SWANCTL_X509AADIR);
678 load_certs(conn, format, "x509crl", SWANCTL_X509CRLDIR);
679 load_certs(conn, format, "x509ac", SWANCTL_X509ACDIR);
680
681 load_keys(conn, format, noprompt, cfg, "rsa", SWANCTL_RSADIR);
682 load_keys(conn, format, noprompt, cfg, "ecdsa", SWANCTL_ECDSADIR);
683 load_keys(conn, format, noprompt, cfg, "pkcs8", SWANCTL_PKCS8DIR);
684
685 load_containers(conn, format, noprompt, cfg, "pkcs12", SWANCTL_PKCS12DIR);
686
687 enumerator = cfg->create_section_enumerator(cfg, "secrets");
688 while (enumerator->enumerate(enumerator, &section))
689 {
690 load_secret(conn, cfg, section, format);
691 }
692 enumerator->destroy(enumerator);
693
694 return 0;
695 }
696
697 static int load_creds(vici_conn_t *conn)
698 {
699 bool clear = FALSE, noprompt = FALSE;
700 command_format_options_t format = COMMAND_FORMAT_NONE;
701 settings_t *cfg;
702 char *arg;
703 int ret;
704
705 while (TRUE)
706 {
707 switch (command_getopt(&arg))
708 {
709 case 'h':
710 return command_usage(NULL);
711 case 'c':
712 clear = TRUE;
713 continue;
714 case 'n':
715 noprompt = TRUE;
716 continue;
717 case 'P':
718 format |= COMMAND_FORMAT_PRETTY;
719 /* fall through to raw */
720 case 'r':
721 format |= COMMAND_FORMAT_RAW;
722 continue;
723 case EOF:
724 break;
725 default:
726 return command_usage("invalid --load-creds option");
727 }
728 break;
729 }
730
731 cfg = settings_create(SWANCTL_CONF);
732 if (!cfg)
733 {
734 fprintf(stderr, "parsing '%s' failed\n", SWANCTL_CONF);
735 return EINVAL;
736 }
737
738 ret = load_creds_cfg(conn, format, cfg, clear, noprompt);
739
740 cfg->destroy(cfg);
741
742 return ret;
743 }
744
745 /**
746 * Register the command.
747 */
748 static void __attribute__ ((constructor))reg()
749 {
750 command_register((command_t) {
751 load_creds, 's', "load-creds", "(re-)load credentials",
752 {"[--raw|--pretty] [--clear] [--noprompt]"},
753 {
754 {"help", 'h', 0, "show usage information"},
755 {"clear", 'c', 0, "clear previously loaded credentials"},
756 {"noprompt", 'n', 0, "do not prompt for passwords"},
757 {"raw", 'r', 0, "dump raw response message"},
758 {"pretty", 'P', 0, "dump raw response message in pretty print"},
759 }
760 });
761 }