a19cd2944f5cf2e12e108437c008a4fee800a386
[platform/upstream/cryptsetup.git] / misc / luks2_keyslot_example / keyslot_test.c
1 /*
2  * Example of LUKS2 kesylot handler (EXAMPLE)
3  *
4  * Copyright (C) 2016-2020 Milan Broz <gmazyland@gmail.com>
5  *
6  * Use:
7  *  - generate LUKS device
8  *  - store passphrase used in previous step remotely (single line w/o \r\n)
9  *  - add new token using this example
10  *  - activate device by token
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25  */
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <json-c/json.h>
33 #include <libssh/libssh.h>
34 #include <libssh/sftp.h>
35 #include "libcryptsetup.h"
36
37 #define TOKEN_NUM 0
38
39 #define PASSWORD_LENGTH 8192
40
41 typedef int (*password_cb_func) (char **password);
42
43 static json_object *get_token_jobj(struct crypt_device *cd, int token)
44 {
45         const char *json_slot;
46
47         /* libcryptsetup API call */
48         if (crypt_token_json_get(cd, token, &json_slot))
49                 return NULL;
50
51         return json_tokener_parse(json_slot);
52 }
53
54 static int download_remote_password(struct crypt_device *cd, ssh_session ssh,
55                                     const char *path, char **password,
56                                     size_t *password_len)
57 {
58         char *pass;
59         size_t pass_len;
60         int r;
61         sftp_attributes sftp_attr = NULL;
62         sftp_session sftp = NULL;
63         sftp_file file = NULL;
64
65
66         sftp = sftp_new(ssh);
67         if (!sftp) {
68                 crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: ");
69                 r = SSH_FX_FAILURE;
70                 goto out;
71         }
72
73         r = sftp_init(sftp);
74         if (r != SSH_OK) {
75                 crypt_log(cd, CRYPT_LOG_ERROR, "Cannot init sftp session: ");
76                 goto out;
77         }
78
79         file = sftp_open(sftp, path, O_RDONLY, 0);
80         if (!file) {
81                 crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: ");
82                 r = SSH_FX_FAILURE;
83                 goto out;
84         }
85
86         sftp_attr = sftp_fstat(file);
87         if (!sftp_attr) {
88                 crypt_log(cd, CRYPT_LOG_ERROR, "Cannot stat sftp file: ");
89                 r = SSH_FX_FAILURE;
90                 goto out;
91         }
92
93         pass_len = sftp_attr->size > PASSWORD_LENGTH ? PASSWORD_LENGTH : sftp_attr->size;
94         pass = malloc(pass_len);
95         if (!pass) {
96                 crypt_log(cd, CRYPT_LOG_ERROR, "Not enough memory.\n");
97                 r = SSH_FX_FAILURE;
98                 goto out;
99         }
100
101         r = sftp_read(file, pass, pass_len);
102         if (r < 0 || (size_t)r != pass_len) {
103                 crypt_log(cd, CRYPT_LOG_ERROR, "Cannot read remote key: ");
104                 r = SSH_FX_FAILURE;
105                 goto out;
106         }
107
108         *password = pass;
109         *password_len = pass_len;
110
111         r = SSH_OK;
112 out:
113         if (r != SSH_OK) {
114                 crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh));
115                 crypt_log(cd, CRYPT_LOG_ERROR, "\n");
116                 free(pass);
117         }
118
119         if (sftp_attr)
120                 sftp_attributes_free(sftp_attr);
121
122         if (file)
123                 sftp_close(file);
124         if (sftp)
125                 sftp_free(sftp);
126         return r == SSH_OK ? 0 : -EINVAL;
127 }
128
129 static ssh_session ssh_session_init(struct crypt_device *cd,
130                                     const char *host,
131                                     const char *user)
132 {
133         int r, port = 22;
134         ssh_session ssh = ssh_new();
135         if (!ssh)
136                 return NULL;
137
138         ssh_options_set(ssh, SSH_OPTIONS_HOST, host);
139         ssh_options_set(ssh, SSH_OPTIONS_USER, user);
140         ssh_options_set(ssh, SSH_OPTIONS_PORT, &port);
141
142         crypt_log(cd, CRYPT_LOG_NORMAL, "Initiating ssh session.\n");
143
144         r = ssh_connect(ssh);
145         if (r != SSH_OK) {
146                 crypt_log(cd, CRYPT_LOG_ERROR, "Connection failed: ");
147                 goto out;
148         }
149
150         r = ssh_is_server_known(ssh);
151         if (r != SSH_SERVER_KNOWN_OK) {
152                 crypt_log(cd, CRYPT_LOG_ERROR, "Server not known: ");
153                 r = SSH_AUTH_ERROR;
154                 goto out;
155         }
156
157         r = SSH_OK;
158
159         /* initialise list of authentication methods. yes, according to official libssh docs... */
160         ssh_userauth_none(ssh, NULL);
161 out:
162         if (r != SSH_OK) {
163                 crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh));
164                 crypt_log(cd, CRYPT_LOG_ERROR, "\n");
165                 ssh_disconnect(ssh);
166                 ssh_free(ssh);
167                 ssh = NULL;
168         }
169
170         return ssh;
171 }
172
173 static void ssh_session_close(ssh_session ssh)
174 {
175         if (ssh) {
176                 ssh_disconnect(ssh);
177                 ssh_free(ssh);
178         }
179 }
180
181 static int _public_key_auth(struct crypt_device *cd, ssh_session ssh)
182 {
183         int r;
184         ssh_key pkey = NULL;
185
186         crypt_log(cd, CRYPT_LOG_DEBUG, "Trying public key authentication method.\n");
187
188         if (!(ssh_userauth_list(ssh, NULL) & SSH_AUTH_METHOD_PUBLICKEY)) {
189                 crypt_log(cd, CRYPT_LOG_ERROR, "Public key auth method not allowed on host.\n");
190                 return SSH_AUTH_ERROR;
191         }
192
193         r = ssh_pki_import_privkey_file("/home/user/.ssh/id_rsa", NULL, NULL, NULL, &pkey);
194         if (r != SSH_OK) {
195                 crypt_log(cd, CRYPT_LOG_ERROR, "Failed to import private key\n");
196
197                 return r;
198         }
199
200         r = ssh_userauth_try_publickey(ssh, NULL, pkey);
201         if (r == SSH_AUTH_SUCCESS) {
202                 crypt_log(cd, CRYPT_LOG_DEBUG, "Public key method accepted.\n");
203                 r = ssh_userauth_publickey(ssh, NULL, pkey);
204         }
205
206         ssh_key_free(pkey);
207
208         if (r != SSH_AUTH_SUCCESS) {
209                 crypt_log(cd, CRYPT_LOG_ERROR, "Public key authentication error: ");
210                 crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh));
211                 crypt_log(cd, CRYPT_LOG_ERROR, "\n");
212         }
213
214         return r;
215 }
216
217 static int _password_auth(struct crypt_device *cd, ssh_session ssh, password_cb_func pcb)
218 {
219         int r = SSH_AUTH_ERROR;
220         char *ssh_password = NULL;
221
222         if (!(ssh_userauth_list(ssh, NULL) & SSH_AUTH_METHOD_PASSWORD)) {
223                 crypt_log(cd, CRYPT_LOG_ERROR, "Password auth method not allowed on host.\n");
224                 return r;
225         }
226
227         if (pcb(&ssh_password)) {
228                 crypt_log(cd, CRYPT_LOG_ERROR, "Failed to process password.\n");
229                 return r;
230         }
231
232         r = ssh_userauth_password(ssh, NULL, ssh_password);
233
234         free(ssh_password);
235
236         if (r != SSH_AUTH_SUCCESS) {
237                 crypt_log(cd, CRYPT_LOG_ERROR, "Password authentication error: ");
238                 crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh));
239                 crypt_log(cd, CRYPT_LOG_ERROR, "\n");
240         }
241
242         return r;
243 }
244
245 static int SSHTEST_token_open(struct crypt_device *cd,
246         int token,
247         char **password,
248         size_t *password_len,
249         void *usrptr)
250 {
251         int r;
252         json_object *jobj_server, *jobj_user, *jobj_path, *jobj_token;
253         ssh_session ssh;
254         password_cb_func pcb = usrptr; /* custom password callback */
255
256         jobj_token = get_token_jobj(cd, token);
257         json_object_object_get_ex(jobj_token, "ssh_server", &jobj_server);
258         json_object_object_get_ex(jobj_token, "ssh_user",   &jobj_user);
259         json_object_object_get_ex(jobj_token, "ssh_path",   &jobj_path);
260
261         ssh = ssh_session_init(cd, json_object_get_string(jobj_server),
262                                json_object_get_string(jobj_user));
263         if (!ssh)
264                 return -EINVAL;
265
266         r = _public_key_auth(cd, ssh);
267
268         /* try password method fallback. superficial example use case for an usrptr */
269         if (r != SSH_AUTH_SUCCESS && pcb) {
270                 crypt_log(cd, CRYPT_LOG_DEBUG, "Trying password method instead.\n");
271                 r = _password_auth(cd, ssh, pcb);
272         }
273
274         if (r == SSH_AUTH_SUCCESS)
275                 r = download_remote_password(cd, ssh, json_object_get_string(jobj_path),
276                                              password, password_len);
277
278         ssh_session_close(ssh);
279
280         return r ? -EINVAL : r;
281 }
282
283 const crypt_token_handler SSHTEST_token = {
284         .name  = "sshkeytest",
285         .open  = SSHTEST_token_open,
286 };
287
288 static int token_add(const char *device, const char *server,
289                    const char *user, const char *path)
290 {
291         struct crypt_device *cd = NULL;
292         json_object *jobj = NULL, *jobj_keyslots;
293         int r;
294
295         r = crypt_token_register(&SSHTEST_token);
296         if (r < 0)
297                 return EXIT_FAILURE;
298
299         r = crypt_init(&cd, device);
300         if (r < 0)
301                 return EXIT_FAILURE;
302
303         r = crypt_load(cd, CRYPT_LUKS2, NULL);
304         if (r < 0) {
305                 crypt_free(cd);
306                 return EXIT_FAILURE;
307         }
308
309         jobj = json_object_new_object();
310         json_object_object_add(jobj, "type", json_object_new_string(SSHTEST_token.name)); /* mandatory */
311
312         jobj_keyslots = json_object_new_array();
313         json_object_array_add(jobj_keyslots, json_object_new_string("0")); /* assign to first keyslot only */
314         json_object_object_add(jobj, "keyslots", jobj_keyslots); /* mandatory array field (may be empty and assigned later */
315
316         /* custom metadata */
317         json_object_object_add(jobj, "ssh_server", json_object_new_string(server));
318         json_object_object_add(jobj, "ssh_user", json_object_new_string(user));
319         json_object_object_add(jobj, "ssh_path", json_object_new_string(path));
320
321         /* libcryptsetup API call */
322         r = crypt_token_json_set(cd, TOKEN_NUM, json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN));
323
324         crypt_free(cd);
325         json_object_put(jobj);
326
327         return EXIT_SUCCESS;
328 }
329
330
331 /* naive implementation of password prompt. Yes it will print out the password on input :) */
332 static int ssh_password_callback(char **ssh_password)
333 {
334         ssize_t i;
335         char *pass = malloc(512);
336
337         if (!pass)
338                 return -ENOMEM;
339
340         fprintf(stdout, "Host asks for password:\n");
341
342         i = read(STDIN_FILENO, pass, 512);
343         if (i > 0) {
344                 pass[i-1] = '\0';
345                 i = 0;
346         } else if (i == 0) { /* EOF */
347                 *pass = '\0';
348                 i = -1;
349         }
350
351         if (!i)
352                 *ssh_password = pass;
353         else
354                 free(pass);
355
356         return i;
357 }
358
359 static int open_by_token(const char *device, const char *name)
360 {
361         struct crypt_device *cd = NULL;
362         int r;
363
364         r = crypt_token_register(&SSHTEST_token);
365         if (r < 0)
366                 return EXIT_FAILURE;
367
368         r = crypt_init(&cd, device);
369         if (r < 0)
370                 return EXIT_FAILURE;
371
372         r = crypt_load(cd, CRYPT_LUKS2, NULL);
373         if (r < 0) {
374                 crypt_free(cd);
375                 return EXIT_FAILURE;
376         }
377
378         r = crypt_activate_by_token(cd, name, TOKEN_NUM, ssh_password_callback, 0);
379
380         crypt_free(cd);
381         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
382 }
383
384 static void keyslot_help(void)
385 {
386         printf("Use parameters:\n add device server user path\n"
387                 " open device name\n");
388         exit(1);
389 }
390
391 int main(int argc, char *argv[])
392 {
393         crypt_set_debug_level(CRYPT_LOG_DEBUG);
394
395         /* Adding slot to device */
396         if (argc == 6 && !strcmp("add", argv[1]))
397                 return token_add(argv[2], argv[3], argv[4], argv[5]);
398
399         /* Key check without activation */
400         if (argc == 3 && !strcmp("open", argv[1]))
401                 return open_by_token(argv[2], NULL);
402
403         /* Key check with activation (requires root) */
404         if (argc == 4 && !strcmp("open", argv[1]))
405                 return open_by_token(argv[2], argv[3]);
406
407         keyslot_help();
408         return 1;
409 }