2 * Example of LUKS2 kesylot handler (EXAMPLE)
4 * Copyright (C) 2016-2021 Milan Broz <gmazyland@gmail.com>
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
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.
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.
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.
32 #include <json-c/json.h>
33 #include <libssh/libssh.h>
34 #include <libssh/sftp.h>
35 #include "libcryptsetup.h"
39 #define PASSWORD_LENGTH 8192
41 typedef int (*password_cb_func) (char **password);
43 static json_object *get_token_jobj(struct crypt_device *cd, int token)
45 const char *json_slot;
47 /* libcryptsetup API call */
48 if (crypt_token_json_get(cd, token, &json_slot))
51 return json_tokener_parse(json_slot);
54 static int download_remote_password(struct crypt_device *cd, ssh_session ssh,
55 const char *path, char **password,
61 sftp_attributes sftp_attr = NULL;
62 sftp_session sftp = NULL;
63 sftp_file file = NULL;
68 crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: ");
75 crypt_log(cd, CRYPT_LOG_ERROR, "Cannot init sftp session: ");
79 file = sftp_open(sftp, path, O_RDONLY, 0);
81 crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: ");
86 sftp_attr = sftp_fstat(file);
88 crypt_log(cd, CRYPT_LOG_ERROR, "Cannot stat sftp file: ");
93 pass_len = sftp_attr->size > PASSWORD_LENGTH ? PASSWORD_LENGTH : sftp_attr->size;
94 pass = malloc(pass_len);
96 crypt_log(cd, CRYPT_LOG_ERROR, "Not enough memory.\n");
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: ");
109 *password_len = pass_len;
114 crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh));
115 crypt_log(cd, CRYPT_LOG_ERROR, "\n");
120 sftp_attributes_free(sftp_attr);
126 return r == SSH_OK ? 0 : -EINVAL;
129 static ssh_session ssh_session_init(struct crypt_device *cd,
134 ssh_session ssh = ssh_new();
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);
142 crypt_log(cd, CRYPT_LOG_NORMAL, "Initiating ssh session.\n");
144 r = ssh_connect(ssh);
146 crypt_log(cd, CRYPT_LOG_ERROR, "Connection failed: ");
150 r = ssh_is_server_known(ssh);
151 if (r != SSH_SERVER_KNOWN_OK) {
152 crypt_log(cd, CRYPT_LOG_ERROR, "Server not known: ");
159 /* initialise list of authentication methods. yes, according to official libssh docs... */
160 ssh_userauth_none(ssh, NULL);
163 crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh));
164 crypt_log(cd, CRYPT_LOG_ERROR, "\n");
173 static void ssh_session_close(ssh_session ssh)
181 static int _public_key_auth(struct crypt_device *cd, ssh_session ssh)
186 crypt_log(cd, CRYPT_LOG_DEBUG, "Trying public key authentication method.\n");
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;
193 r = ssh_pki_import_privkey_file("/home/user/.ssh/id_rsa", NULL, NULL, NULL, &pkey);
195 crypt_log(cd, CRYPT_LOG_ERROR, "Failed to import private key\n");
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);
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");
217 static int _password_auth(struct crypt_device *cd, ssh_session ssh, password_cb_func pcb)
219 int r = SSH_AUTH_ERROR;
220 char *ssh_password = NULL;
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");
227 if (pcb(&ssh_password)) {
228 crypt_log(cd, CRYPT_LOG_ERROR, "Failed to process password.\n");
232 r = ssh_userauth_password(ssh, NULL, ssh_password);
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");
245 static int SSHTEST_token_open(struct crypt_device *cd,
248 size_t *password_len,
252 json_object *jobj_server, *jobj_user, *jobj_path, *jobj_token;
254 password_cb_func pcb = usrptr; /* custom password callback */
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);
261 ssh = ssh_session_init(cd, json_object_get_string(jobj_server),
262 json_object_get_string(jobj_user));
266 r = _public_key_auth(cd, ssh);
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);
274 if (r == SSH_AUTH_SUCCESS)
275 r = download_remote_password(cd, ssh, json_object_get_string(jobj_path),
276 password, password_len);
278 ssh_session_close(ssh);
280 return r ? -EINVAL : r;
283 const crypt_token_handler SSHTEST_token = {
284 .name = "sshkeytest",
285 .open = SSHTEST_token_open,
288 static int token_add(const char *device, const char *server,
289 const char *user, const char *path)
291 struct crypt_device *cd = NULL;
292 json_object *jobj = NULL, *jobj_keyslots;
295 r = crypt_token_register(&SSHTEST_token);
299 r = crypt_init(&cd, device);
303 r = crypt_load(cd, CRYPT_LUKS2, NULL);
309 jobj = json_object_new_object();
310 json_object_object_add(jobj, "type", json_object_new_string(SSHTEST_token.name)); /* mandatory */
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 */
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));
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));
325 json_object_put(jobj);
331 /* naive implementation of password prompt. Yes it will print out the password on input :) */
332 static int ssh_password_callback(char **ssh_password)
335 char *pass = malloc(512);
340 fprintf(stdout, "Host asks for password:\n");
342 i = read(STDIN_FILENO, pass, 512);
346 } else if (i == 0) { /* EOF */
352 *ssh_password = pass;
359 static int open_by_token(const char *device, const char *name)
361 struct crypt_device *cd = NULL;
364 r = crypt_token_register(&SSHTEST_token);
368 r = crypt_init(&cd, device);
372 r = crypt_load(cd, CRYPT_LUKS2, NULL);
378 r = crypt_activate_by_token(cd, name, TOKEN_NUM, ssh_password_callback, 0);
381 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
384 static void keyslot_help(void)
386 printf("Use parameters:\n add device server user path\n"
387 " open device name\n");
391 int main(int argc, char *argv[])
393 crypt_set_debug_level(CRYPT_LOG_DEBUG);
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]);
399 /* Key check without activation */
400 if (argc == 3 && !strcmp("open", argv[1]))
401 return open_by_token(argv[2], NULL);
403 /* Key check with activation (requires root) */
404 if (argc == 4 && !strcmp("open", argv[1]))
405 return open_by_token(argv[2], argv[3]);