Merge branch 'upstream' into tizen
[platform/upstream/cryptsetup.git] / tokens / ssh / cryptsetup-ssh.c
1 /*
2  * Example of LUKS2 token storing third party metadata (EXPERIMENTAL EXAMPLE)
3  *
4  * Copyright (C) 2016-2023 Milan Broz
5  * Copyright (C) 2021-2023 Vojtech Trefny
6  *
7  * Use:
8  *  - generate ssh example token
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <argp.h>
31 #include <json-c/json.h>
32 #include <termios.h>
33 #include <stdbool.h>
34 #include "libcryptsetup.h"
35 #include "ssh-utils.h"
36 #include "../src/cryptsetup.h"
37
38 #define TOKEN_NAME "ssh"
39
40 #define l_err(cd, x...) crypt_logf(cd, CRYPT_LOG_ERROR, x)
41 #define l_dbg(cd, x...) crypt_logf(cd, CRYPT_LOG_DEBUG, x)
42
43 #define OPT_SSH_SERVER  1
44 #define OPT_SSH_USER    2
45 #define OPT_SSH_PATH    3
46 #define OPT_KEY_PATH    4
47 #define OPT_DEBUG       5
48 #define OPT_DEBUG_JSON  6
49 #define OPT_KEY_SLOT    7
50
51 void tools_cleanup(void)
52 {
53 }
54
55
56 static int token_add(
57                 const char *device,
58                 const char *server,
59                 const char *user,
60                 const char *path,
61                 const char *keypath,
62                 int keyslot)
63
64 {
65         struct crypt_device *cd;
66         json_object *jobj = NULL;
67         json_object *jobj_keyslots = NULL;
68         const char *string_token;
69         int r, token;
70
71         r = crypt_init(&cd, device);
72         if (r)
73                 return r;
74
75         r = crypt_load(cd, CRYPT_LUKS2, NULL);
76         if (r) {
77                 l_err(cd, _("Device %s is not a valid LUKS device."), device);
78                 goto out;
79         }
80
81         r = -EINVAL;
82         jobj = json_object_new_object();
83         if (!jobj)
84                 goto out;
85
86         /* type is mandatory field in all tokens and must match handler name member */
87         json_object_object_add(jobj, "type", json_object_new_string(TOKEN_NAME));
88
89         jobj_keyslots = json_object_new_array();
90
91         /* mandatory array field (may be empty and assigned later */
92         json_object_object_add(jobj, "keyslots", jobj_keyslots);
93
94         /* custom metadata */
95         json_object_object_add(jobj, "ssh_server", json_object_new_string(server));
96         json_object_object_add(jobj, "ssh_user", json_object_new_string(user));
97         json_object_object_add(jobj, "ssh_path", json_object_new_string(path));
98         json_object_object_add(jobj, "ssh_keypath", json_object_new_string(keypath));
99
100         string_token = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN);
101         if (!string_token) {
102                 r = -EINVAL;
103                 goto out;
104         }
105
106         l_dbg(cd, "Token JSON: %s", string_token);
107
108         r = crypt_token_json_set(cd, CRYPT_ANY_TOKEN, string_token);
109         if (r < 0) {
110                 l_err(cd, _("Failed to write ssh token json."));
111                 goto out;
112         }
113
114         token = r;
115         r = crypt_token_assign_keyslot(cd, token, keyslot);
116         if (r != token) {
117                 crypt_token_json_set(cd, token, NULL);
118                 r = -EINVAL;
119         }
120 out:
121         json_object_put(jobj);
122         crypt_free(cd);
123         return r;
124 }
125
126 const char *argp_program_version = "cryptsetup-ssh " PACKAGE_VERSION;
127
128 static char doc[] = N_("Experimental cryptsetup plugin for unlocking LUKS2 devices with token connected " \
129                        "to an SSH server\v" \
130                        "This plugin currently allows only adding a token to an existing key slot.\n\n" \
131                        "Specified SSH server must contain a key file on the specified path with " \
132                        "a passphrase for an existing key slot on the device.\n" \
133                        "Provided credentials will be used by cryptsetup to get the password when " \
134                        "opening the device using the token.\n\n" \
135                        "Note: The information provided when adding the token (SSH server address, user and paths) " \
136                        "will be stored in the LUKS2 header in plaintext.");
137
138 static char args_doc[] = N_("<action> <device>");
139
140 static struct argp_option options[] = {
141         {0,             0,              0,        0, N_("Options for the 'add' action:")},
142         {"ssh-server",  OPT_SSH_SERVER, "STRING", 0, N_("IP address/URL of the remote server for this token")},
143         {"ssh-user",    OPT_SSH_USER,   "STRING", 0, N_("Username used for the remote server")},
144         {"ssh-path",    OPT_SSH_PATH,   "STRING", 0, N_("Path to the key file on the remote server")},
145         {"ssh-keypath", OPT_KEY_PATH,   "STRING", 0, N_("Path to the SSH key for connecting to the remote server")},
146         {"key-slot",    OPT_KEY_SLOT,   "NUM",    0, N_("Keyslot to assign the token to. If not specified, token will "\
147                                                         "be assigned to the first keyslot matching provided passphrase.")},
148         {0,             0,              0,        0, N_("Generic options:")},
149         {"verbose",     'v',            0,        0, N_("Shows more detailed error messages")},
150         {"debug",       OPT_DEBUG,      0,        0, N_("Show debug messages")},
151         {"debug-json",  OPT_DEBUG_JSON, 0,        0, N_("Show debug messages including JSON metadata")},
152         { NULL,         0,              0, 0, NULL }
153 };
154
155 struct arguments {
156         char *device;
157         char *action;
158         char *ssh_server;
159         char *ssh_user;
160         char *ssh_path;
161         char *ssh_keypath;
162         int keyslot;
163         int verbose;
164         int debug;
165         int debug_json;
166 };
167
168 static error_t
169 parse_opt (int key, char *arg, struct argp_state *state) {
170         struct arguments *arguments = state->input;
171
172         switch (key) {
173         case OPT_SSH_SERVER:
174                 arguments->ssh_server = arg;
175                 break;
176         case OPT_SSH_USER:
177                 arguments->ssh_user = arg;
178                 break;
179         case OPT_SSH_PATH:
180                 arguments->ssh_path = arg;
181                 break;
182         case OPT_KEY_PATH:
183                 arguments->ssh_keypath = arg;
184                 break;
185         case OPT_KEY_SLOT:
186                 arguments->keyslot = atoi(arg);
187                 break;
188         case 'v':
189                 arguments->verbose = 1;
190                 break;
191         case OPT_DEBUG:
192                 arguments->debug = 1;
193                 break;
194         case OPT_DEBUG_JSON:
195                 arguments->debug = 1;
196                 arguments->debug_json = 1;
197                 break;
198         case ARGP_KEY_NO_ARGS:
199                 argp_usage(state);
200                 break;
201         case ARGP_KEY_ARG:
202                 arguments->action = arg;
203                 arguments->device = state->argv[state->next];
204                 state->next = state->argc;
205                 break;
206         default:
207                 return ARGP_ERR_UNKNOWN;
208         }
209
210         return 0;
211 }
212
213 static struct argp argp = { options, parse_opt, args_doc, doc };
214
215
216 static void _log(int level, const char *msg, void *usrptr)
217 {
218         struct arguments *arguments = (struct arguments *)usrptr;
219
220         switch (level) {
221         case CRYPT_LOG_NORMAL:
222                 fprintf(stdout, "%s", msg);
223                 break;
224         case CRYPT_LOG_VERBOSE:
225                 if (arguments && arguments->verbose)
226                         fprintf(stdout, "%s", msg);
227                 break;
228         case CRYPT_LOG_ERROR:
229                 fprintf(stderr, "%s", msg);
230                 break;
231         case CRYPT_LOG_DEBUG_JSON:
232                 if (arguments && arguments->debug_json)
233                         fprintf(stdout, "# %s", msg);
234                 break;
235         case CRYPT_LOG_DEBUG:
236                 if (arguments && arguments->debug)
237                         fprintf(stdout, "# %s", msg);
238                 break;
239         }
240 }
241
242 static int get_keyslot_for_passphrase(struct arguments *arguments, const char *pin)
243 {
244         int r = 0;
245         ssh_key pkey;
246         ssh_session ssh;
247         char *password = NULL;
248         size_t password_len = 0;
249         struct crypt_device *cd = NULL;
250         char *ssh_pass = NULL;
251         size_t key_size = 0;
252         char *prompt = NULL;
253
254         r = crypt_init(&cd, arguments->device);
255         if (r < 0)
256                 return r;
257         crypt_set_log_callback(cd, &_log, arguments);
258
259         r = ssh_pki_import_privkey_file(arguments->ssh_keypath, pin, NULL, NULL, &pkey);
260         if (r != SSH_OK) {
261                 if (r == SSH_EOF) {
262                         crypt_log(cd, CRYPT_LOG_ERROR, _("Failed to open and import private key:\n"));
263                         crypt_free(cd);
264                         return -EINVAL;
265                 } else {
266                         _log(CRYPT_LOG_ERROR, _("Failed to import private key (password protected?).\n"), NULL);
267                         /* TRANSLATORS: SSH credentials prompt, e.g. "user@server's password: " */
268                         r = asprintf(&prompt, _("%s@%s's password: "), arguments->ssh_user, arguments->ssh_server);
269                         if (r < 0) {
270                                 crypt_safe_free(ssh_pass);
271                                 crypt_free(cd);
272                                 return -EINVAL;
273                         }
274
275                         r = tools_get_key(prompt, &ssh_pass, &key_size, 0, 0, NULL, 0, 0, 0, cd);
276                         if (r < 0) {
277                                 free(prompt);
278                                 crypt_safe_free(ssh_pass);
279                                 crypt_free(cd);
280                                 return -EINVAL;
281                         }
282
283                         /* now try again with the password */
284                         r = get_keyslot_for_passphrase(arguments, ssh_pass);
285
286                         crypt_safe_free(ssh_pass);
287                         crypt_free(cd);
288                         free(prompt);
289
290                         return r;
291                 }
292         }
293
294         ssh = sshplugin_session_init(cd, arguments->ssh_server, arguments->ssh_user);
295         if (!ssh) {
296                 ssh_key_free(pkey);
297                 crypt_free(cd);
298                 return -EINVAL;
299         }
300
301         r = sshplugin_public_key_auth(cd, ssh, pkey);
302         ssh_key_free(pkey);
303
304         if (r != SSH_AUTH_SUCCESS) {
305                 crypt_free(cd);
306                 return r;
307         }
308
309         r = sshplugin_download_password(cd, ssh, arguments->ssh_path, &password, &password_len);
310         if (r < 0) {
311                 ssh_disconnect(ssh);
312                 ssh_free(ssh);
313                 crypt_free(cd);
314                 return r;
315         }
316
317         ssh_disconnect(ssh);
318         ssh_free(ssh);
319
320         r = crypt_load(cd, CRYPT_LUKS2, NULL);
321         if (r < 0) {
322                 crypt_safe_memzero(password, password_len);
323                 free(password);
324                 crypt_free(cd);
325                 return r;
326         }
327
328         r = crypt_activate_by_passphrase(cd, NULL, CRYPT_ANY_SLOT, password, password_len, 0);
329         if (r < 0) {
330                 crypt_safe_memzero(password, password_len);
331                 free(password);
332                 crypt_free(cd);
333                 return r;
334         }
335
336         arguments->keyslot = r;
337
338         crypt_safe_memzero(password, password_len);
339         free(password);
340         crypt_free(cd);
341
342         return 0;
343 }
344
345 int main(int argc, char *argv[])
346 {
347         int ret = 0;
348         struct arguments arguments = { 0 };
349         arguments.keyslot = CRYPT_ANY_SLOT;
350
351         setlocale(LC_ALL, "");
352         bindtextdomain(PACKAGE, LOCALEDIR);
353         textdomain(PACKAGE);
354
355         ret = argp_parse (&argp, argc, argv, 0, 0, &arguments);
356         if (ret != 0) {
357                 printf(_("Failed to parse arguments.\n"));
358                 return EXIT_FAILURE;
359         }
360
361         crypt_set_log_callback(NULL, _log, &arguments);
362         if (arguments.debug)
363                 crypt_set_debug_level(CRYPT_DEBUG_ALL);
364         if (arguments.debug_json)
365                 crypt_set_debug_level(CRYPT_DEBUG_JSON);
366
367         if (arguments.action == NULL) {
368                 printf(_("An action must be specified\n"));
369                 return EXIT_FAILURE;
370         }
371
372         if (strcmp("add", arguments.action) == 0) {
373                 if (!arguments.device) {
374                         printf(_("Device must be specified for '%s' action.\n"), arguments.action);
375                         return EXIT_FAILURE;
376                 }
377
378                 if (!arguments.ssh_server) {
379                         printf(_("SSH server must be specified for '%s' action.\n"), arguments.action);
380                         return EXIT_FAILURE;
381                 }
382
383                 if (!arguments.ssh_user) {
384                         printf(_("SSH user must be specified for '%s' action.\n"), arguments.action);
385                         return EXIT_FAILURE;
386                 }
387
388                 if (!arguments.ssh_path) {
389                         printf(_("SSH path must be specified for '%s' action.\n"), arguments.action);
390                         return EXIT_FAILURE;
391                 }
392
393                 if (!arguments.ssh_keypath) {
394                         printf(_("SSH key path must be specified for '%s' action.\n"), arguments.action);
395                         return EXIT_FAILURE;
396                 }
397
398                 if (arguments.keyslot == CRYPT_ANY_SLOT) {
399                         ret = get_keyslot_for_passphrase(&arguments, NULL);
400                         if (ret != 0) {
401                                 printf(_("Failed open %s using provided credentials.\n"), arguments.device);
402                                 return EXIT_FAILURE;
403                         }
404                 }
405
406                 ret = token_add(arguments.device,
407                                 arguments.ssh_server,
408                                 arguments.ssh_user,
409                                 arguments.ssh_path,
410                                 arguments.ssh_keypath,
411                                 arguments.keyslot);
412                 if (ret < 0)
413                         return EXIT_FAILURE;
414                 else
415                         return EXIT_SUCCESS;
416         } else {
417                 printf(_("Only 'add' action is currently supported by this plugin.\n"));
418                 return EXIT_FAILURE;
419         }
420 }