swanctl: Load different private keys with load-creds
[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
25 #include <credentials/sets/callback_cred.h>
26
27 /**
28 * Load a single certificate over vici
29 */
30 static bool load_cert(vici_conn_t *conn, bool raw, char *dir,
31 char *type, chunk_t data)
32 {
33 vici_req_t *req;
34 vici_res_t *res;
35 bool ret = TRUE;
36
37 req = vici_begin("load-cert");
38
39 vici_add_key_valuef(req, "type", "%s", type);
40 vici_add_key_value(req, "data", data.ptr, data.len);
41
42 res = vici_submit(req, conn);
43 if (!res)
44 {
45 fprintf(stderr, "load-cert request failed: %s\n", strerror(errno));
46 return FALSE;
47 }
48 if (raw)
49 {
50 vici_dump(res, "load-cert reply", stdout);
51 }
52 else if (!streq(vici_find_str(res, "no", "success"), "yes"))
53 {
54 fprintf(stderr, "loading '%s' failed: %s\n",
55 dir, vici_find_str(res, "", "errmsg"));
56 ret = FALSE;
57 }
58 vici_free_res(res);
59 return ret;
60 }
61
62 /**
63 * Load certficiates from a directory
64 */
65 static void load_certs(vici_conn_t *conn, bool raw, char *type, char *dir)
66 {
67 enumerator_t *enumerator;
68 struct stat st;
69 chunk_t *map;
70 char *path;
71
72 enumerator = enumerator_create_directory(dir);
73 if (enumerator)
74 {
75 while (enumerator->enumerate(enumerator, NULL, &path, &st))
76 {
77 if (S_ISREG(st.st_mode))
78 {
79 map = chunk_map(path, FALSE);
80 if (map)
81 {
82 load_cert(conn, raw, path, type, *map);
83 chunk_unmap(map);
84 }
85 else
86 {
87 fprintf(stderr, "mapping '%s' failed: %s, skipped\n",
88 path, strerror(errno));
89 }
90 }
91 }
92 enumerator->destroy(enumerator);
93 }
94 }
95
96 /**
97 * Load a single private key over vici
98 */
99 static bool load_key(vici_conn_t *conn, bool raw, char *dir,
100 char *type, chunk_t data)
101 {
102 vici_req_t *req;
103 vici_res_t *res;
104 bool ret = TRUE;
105
106 req = vici_begin("load-key");
107
108 vici_add_key_valuef(req, "type", "%s", type);
109 vici_add_key_value(req, "data", data.ptr, data.len);
110
111 res = vici_submit(req, conn);
112 if (!res)
113 {
114 fprintf(stderr, "load-key request failed: %s\n", strerror(errno));
115 return FALSE;
116 }
117 if (raw)
118 {
119 vici_dump(res, "load-key reply", stdout);
120 }
121 else if (!streq(vici_find_str(res, "no", "success"), "yes"))
122 {
123 fprintf(stderr, "loading '%s' failed: %s\n",
124 dir, vici_find_str(res, "", "errmsg"));
125 ret = FALSE;
126 }
127 vici_free_res(res);
128 return ret;
129 }
130
131 /**
132 * Callback function to prompt for private key passwords
133 */
134 CALLBACK(password_cb, shared_key_t*,
135 char *prompt, shared_key_type_t type,
136 identification_t *me, identification_t *other,
137 id_match_t *match_me, id_match_t *match_other)
138 {
139 char *pwd;
140
141 if (type != SHARED_PRIVATE_KEY_PASS)
142 {
143 return NULL;
144 }
145 pwd = getpass(prompt);
146 if (!pwd || strlen(pwd) == 0)
147 {
148 return NULL;
149 }
150 if (match_me)
151 {
152 *match_me = ID_MATCH_PERFECT;
153 }
154 if (match_other)
155 {
156 *match_other = ID_MATCH_PERFECT;
157 }
158 return shared_key_create(type, chunk_clone(chunk_from_str(pwd)));
159 }
160
161 /**
162 * Try to parse a potentially encrypted private key
163 */
164 static private_key_t* decrypt_key(char *name, char *type, chunk_t encoding)
165 {
166 key_type_t kt = KEY_ANY;
167 private_key_t *private;
168 callback_cred_t *cb;
169 char buf[128];
170
171 if (streq(type, "rsa"))
172 {
173 kt = KEY_RSA;
174 }
175 else if (streq(type, "ecdsa"))
176 {
177 kt = KEY_ECDSA;
178 }
179
180 snprintf(buf, sizeof(buf), "Password for '%s': ", name);
181
182 cb = callback_cred_create_shared(password_cb, buf);
183 lib->credmgr->add_set(lib->credmgr, &cb->set);
184
185 private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, kt,
186 BUILD_BLOB_PEM, encoding, BUILD_END);
187
188 lib->credmgr->remove_set(lib->credmgr, &cb->set);
189 cb->destroy(cb);
190
191 return private;
192 }
193
194 /**
195 * Try to decrypt and load a private key
196 */
197 static bool load_encrypted_key(vici_conn_t *conn, bool raw,
198 char *rel, char *path, char *type, chunk_t data)
199 {
200 private_key_t *private;
201 bool loaded = FALSE;
202 chunk_t encoding;
203
204 private = decrypt_key(rel, type, data);
205 if (private)
206 {
207 if (private->get_encoding(private, PRIVKEY_ASN1_DER,
208 &encoding))
209 {
210 switch (private->get_type(private))
211 {
212 case KEY_RSA:
213 loaded = load_key(conn, raw, path, "rsa", encoding);
214 break;
215 case KEY_ECDSA:
216 loaded = load_key(conn, raw, path, "ecdsa", encoding);
217 break;
218 default:
219 break;
220 }
221 chunk_clear(&encoding);
222 }
223 private->destroy(private);
224 }
225 return loaded;
226 }
227
228 /**
229 * Load private keys from a directory
230 */
231 static void load_keys(vici_conn_t *conn, bool raw, bool noprompt,
232 char *type, char *dir)
233 {
234 enumerator_t *enumerator;
235 struct stat st;
236 chunk_t *map;
237 char *path, *rel;
238
239 enumerator = enumerator_create_directory(dir);
240 if (enumerator)
241 {
242 while (enumerator->enumerate(enumerator, &rel, &path, &st))
243 {
244 if (S_ISREG(st.st_mode))
245 {
246 map = chunk_map(path, FALSE);
247 if (map)
248 {
249 if (noprompt ||
250 !load_encrypted_key(conn, raw, rel, path, type, *map))
251 {
252 load_key(conn, raw, path, type, *map);
253 }
254 chunk_unmap(map);
255 }
256 else
257 {
258 fprintf(stderr, "mapping '%s' failed: %s, skipped\n",
259 path, strerror(errno));
260 }
261 }
262 }
263 enumerator->destroy(enumerator);
264 }
265 }
266
267 /**
268 * Clear all currently loaded credentials
269 */
270 static bool clear_creds(vici_conn_t *conn, bool raw)
271 {
272 vici_res_t *res;
273
274 res = vici_submit(vici_begin("clear-creds"), conn);
275 if (!res)
276 {
277 fprintf(stderr, "clear-creds request failed: %s\n", strerror(errno));
278 return FALSE;
279 }
280 if (raw)
281 {
282 vici_dump(res, "clear-creds reply", stdout);
283 }
284 vici_free_res(res);
285 return TRUE;
286 }
287
288 static int load_creds(vici_conn_t *conn)
289 {
290 bool raw = FALSE, clear = FALSE, noprompt = FALSE;
291 char *arg;
292
293 while (TRUE)
294 {
295 switch (command_getopt(&arg))
296 {
297 case 'h':
298 return command_usage(NULL);
299 case 'c':
300 clear = TRUE;
301 continue;
302 case 'n':
303 noprompt = TRUE;
304 continue;
305 case 'r':
306 raw = TRUE;
307 continue;
308 case EOF:
309 break;
310 default:
311 return command_usage("invalid --load-creds option");
312 }
313 break;
314 }
315
316 if (clear)
317 {
318 if (!clear_creds(conn, raw))
319 {
320 return ECONNREFUSED;
321 }
322 }
323
324 load_certs(conn, raw, "x509", SWANCTL_X509DIR);
325 load_certs(conn, raw, "x509ca", SWANCTL_X509CADIR);
326 load_certs(conn, raw, "x509aa", SWANCTL_X509AADIR);
327 load_certs(conn, raw, "x509crl", SWANCTL_X509CRLDIR);
328 load_certs(conn, raw, "x509ac", SWANCTL_X509ACDIR);
329
330 load_keys(conn, raw, noprompt, "rsa", SWANCTL_RSADIR);
331 load_keys(conn, raw, noprompt, "ecdsa", SWANCTL_ECDSADIR);
332 load_keys(conn, raw, noprompt, "any", SWANCTL_PKCS8DIR);
333
334 return 0;
335 }
336
337 /**
338 * Register the command.
339 */
340 static void __attribute__ ((constructor))reg()
341 {
342 command_register((command_t) {
343 load_creds, 's', "load-creds", "(re-)load credentials",
344 {"[--raw]"},
345 {
346 {"help", 'h', 0, "show usage information"},
347 {"clear", 'c', 0, "clear previously loaded credentials"},
348 {"noprompt", 'n', 0, "do not prompt for passwords"},
349 {"raw", 'r', 0, "dump raw response message"},
350 }
351 });
352 }