diff options
Diffstat (limited to 'misc/luks2_keyslot_example')
-rw-r--r-- | misc/luks2_keyslot_example/Makefile | 24 | ||||
-rw-r--r-- | misc/luks2_keyslot_example/README | 3 | ||||
-rw-r--r-- | misc/luks2_keyslot_example/keyslot_test.c | 409 | ||||
-rw-r--r-- | misc/luks2_keyslot_example/keyslot_test_remote_pass.c | 264 |
4 files changed, 700 insertions, 0 deletions
diff --git a/misc/luks2_keyslot_example/Makefile b/misc/luks2_keyslot_example/Makefile new file mode 100644 index 0000000..777eafa --- /dev/null +++ b/misc/luks2_keyslot_example/Makefile @@ -0,0 +1,24 @@ +TARGET=keyslot_test +CFLAGS=-O0 -g -Wall -D_GNU_SOURCE -I ../../lib/ +LDLIBS=-ljson-c -luuid -lgcrypt -ldevmapper -lpthread -lssh +CC=gcc + +TARGET2=keyslot_test_remote_pass + +SOURCES=keyslot_test.c +OBJECTS=$(SOURCES:.c=.o) +SOURCES2=keyslot_test_remote_pass.c +OBJECTS2=$(SOURCES2:.c=.o) + +all: $(TARGET) $(TARGET2) $(TARGET4) + +$(TARGET): $(OBJECTS) ../../.libs/libcryptsetup.a + $(CC) -o $@ $^ $(LDLIBS) + +$(TARGET2): $(OBJECTS2) ../../.libs/libcryptsetup.a + $(CC) -o $@ $^ $(LDLIBS) + +clean: + rm -f *.o *~ core $(TARGET) $(TARGET2) + +.PHONY: clean diff --git a/misc/luks2_keyslot_example/README b/misc/luks2_keyslot_example/README new file mode 100644 index 0000000..ac4ab6b --- /dev/null +++ b/misc/luks2_keyslot_example/README @@ -0,0 +1,3 @@ +to create LUKSv2 container run: + +$(top_level_dir)/src/cryptsetup --type luks2 luksFormat /dev/xxx diff --git a/misc/luks2_keyslot_example/keyslot_test.c b/misc/luks2_keyslot_example/keyslot_test.c new file mode 100644 index 0000000..5b8cc9a --- /dev/null +++ b/misc/luks2_keyslot_example/keyslot_test.c @@ -0,0 +1,409 @@ +/* + * Example of LUKS2 kesylot handler (EXAMPLE) + * + * Copyright (C) 2016-2021 Milan Broz <gmazyland@gmail.com> + * + * Use: + * - generate LUKS device + * - store passphrase used in previous step remotely (single line w/o \r\n) + * - add new token using this example + * - activate device by token + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <json-c/json.h> +#include <libssh/libssh.h> +#include <libssh/sftp.h> +#include "libcryptsetup.h" + +#define TOKEN_NUM 0 + +#define PASSWORD_LENGTH 8192 + +typedef int (*password_cb_func) (char **password); + +static json_object *get_token_jobj(struct crypt_device *cd, int token) +{ + const char *json_slot; + + /* libcryptsetup API call */ + if (crypt_token_json_get(cd, token, &json_slot)) + return NULL; + + return json_tokener_parse(json_slot); +} + +static int download_remote_password(struct crypt_device *cd, ssh_session ssh, + const char *path, char **password, + size_t *password_len) +{ + char *pass; + size_t pass_len; + int r; + sftp_attributes sftp_attr = NULL; + sftp_session sftp = NULL; + sftp_file file = NULL; + + + sftp = sftp_new(ssh); + if (!sftp) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_init(sftp); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot init sftp session: "); + goto out; + } + + file = sftp_open(sftp, path, O_RDONLY, 0); + if (!file) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + sftp_attr = sftp_fstat(file); + if (!sftp_attr) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot stat sftp file: "); + r = SSH_FX_FAILURE; + goto out; + } + + pass_len = sftp_attr->size > PASSWORD_LENGTH ? PASSWORD_LENGTH : sftp_attr->size; + pass = malloc(pass_len); + if (!pass) { + crypt_log(cd, CRYPT_LOG_ERROR, "Not enough memory.\n"); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_read(file, pass, pass_len); + if (r < 0 || (size_t)r != pass_len) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot read remote key: "); + r = SSH_FX_FAILURE; + goto out; + } + + *password = pass; + *password_len = pass_len; + + r = SSH_OK; +out: + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + free(pass); + } + + if (sftp_attr) + sftp_attributes_free(sftp_attr); + + if (file) + sftp_close(file); + if (sftp) + sftp_free(sftp); + return r == SSH_OK ? 0 : -EINVAL; +} + +static ssh_session ssh_session_init(struct crypt_device *cd, + const char *host, + const char *user) +{ + int r, port = 22; + ssh_session ssh = ssh_new(); + if (!ssh) + return NULL; + + ssh_options_set(ssh, SSH_OPTIONS_HOST, host); + ssh_options_set(ssh, SSH_OPTIONS_USER, user); + ssh_options_set(ssh, SSH_OPTIONS_PORT, &port); + + crypt_log(cd, CRYPT_LOG_NORMAL, "Initiating ssh session.\n"); + + r = ssh_connect(ssh); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Connection failed: "); + goto out; + } + + r = ssh_is_server_known(ssh); + if (r != SSH_SERVER_KNOWN_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Server not known: "); + r = SSH_AUTH_ERROR; + goto out; + } + + r = SSH_OK; + + /* initialise list of authentication methods. yes, according to official libssh docs... */ + ssh_userauth_none(ssh, NULL); +out: + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + ssh_disconnect(ssh); + ssh_free(ssh); + ssh = NULL; + } + + return ssh; +} + +static void ssh_session_close(ssh_session ssh) +{ + if (ssh) { + ssh_disconnect(ssh); + ssh_free(ssh); + } +} + +static int _public_key_auth(struct crypt_device *cd, ssh_session ssh) +{ + int r; + ssh_key pkey = NULL; + + crypt_log(cd, CRYPT_LOG_DEBUG, "Trying public key authentication method.\n"); + + if (!(ssh_userauth_list(ssh, NULL) & SSH_AUTH_METHOD_PUBLICKEY)) { + crypt_log(cd, CRYPT_LOG_ERROR, "Public key auth method not allowed on host.\n"); + return SSH_AUTH_ERROR; + } + + r = ssh_pki_import_privkey_file("/home/user/.ssh/id_rsa", NULL, NULL, NULL, &pkey); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Failed to import private key\n"); + + return r; + } + + r = ssh_userauth_try_publickey(ssh, NULL, pkey); + if (r == SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_DEBUG, "Public key method accepted.\n"); + r = ssh_userauth_publickey(ssh, NULL, pkey); + } + + ssh_key_free(pkey); + + if (r != SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_ERROR, "Public key authentication error: "); + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + } + + return r; +} + +static int _password_auth(struct crypt_device *cd, ssh_session ssh, password_cb_func pcb) +{ + int r = SSH_AUTH_ERROR; + char *ssh_password = NULL; + + if (!(ssh_userauth_list(ssh, NULL) & SSH_AUTH_METHOD_PASSWORD)) { + crypt_log(cd, CRYPT_LOG_ERROR, "Password auth method not allowed on host.\n"); + return r; + } + + if (pcb(&ssh_password)) { + crypt_log(cd, CRYPT_LOG_ERROR, "Failed to process password.\n"); + return r; + } + + r = ssh_userauth_password(ssh, NULL, ssh_password); + + free(ssh_password); + + if (r != SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_ERROR, "Password authentication error: "); + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + } + + return r; +} + +static int SSHTEST_token_open(struct crypt_device *cd, + int token, + char **password, + size_t *password_len, + void *usrptr) +{ + int r; + json_object *jobj_server, *jobj_user, *jobj_path, *jobj_token; + ssh_session ssh; + password_cb_func pcb = usrptr; /* custom password callback */ + + jobj_token = get_token_jobj(cd, token); + json_object_object_get_ex(jobj_token, "ssh_server", &jobj_server); + json_object_object_get_ex(jobj_token, "ssh_user", &jobj_user); + json_object_object_get_ex(jobj_token, "ssh_path", &jobj_path); + + ssh = ssh_session_init(cd, json_object_get_string(jobj_server), + json_object_get_string(jobj_user)); + if (!ssh) + return -EINVAL; + + r = _public_key_auth(cd, ssh); + + /* try password method fallback. superficial example use case for an usrptr */ + if (r != SSH_AUTH_SUCCESS && pcb) { + crypt_log(cd, CRYPT_LOG_DEBUG, "Trying password method instead.\n"); + r = _password_auth(cd, ssh, pcb); + } + + if (r == SSH_AUTH_SUCCESS) + r = download_remote_password(cd, ssh, json_object_get_string(jobj_path), + password, password_len); + + ssh_session_close(ssh); + + return r ? -EINVAL : r; +} + +const crypt_token_handler SSHTEST_token = { + .name = "sshkeytest", + .open = SSHTEST_token_open, +}; + +static int token_add(const char *device, const char *server, + const char *user, const char *path) +{ + struct crypt_device *cd = NULL; + json_object *jobj = NULL, *jobj_keyslots; + int r; + + r = crypt_token_register(&SSHTEST_token); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + jobj = json_object_new_object(); + json_object_object_add(jobj, "type", json_object_new_string(SSHTEST_token.name)); /* mandatory */ + + jobj_keyslots = json_object_new_array(); + json_object_array_add(jobj_keyslots, json_object_new_string("0")); /* assign to first keyslot only */ + json_object_object_add(jobj, "keyslots", jobj_keyslots); /* mandatory array field (may be empty and assigned later */ + + /* custom metadata */ + json_object_object_add(jobj, "ssh_server", json_object_new_string(server)); + json_object_object_add(jobj, "ssh_user", json_object_new_string(user)); + json_object_object_add(jobj, "ssh_path", json_object_new_string(path)); + + /* libcryptsetup API call */ + r = crypt_token_json_set(cd, TOKEN_NUM, json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN)); + + crypt_free(cd); + json_object_put(jobj); + + return EXIT_SUCCESS; +} + + +/* naive implementation of password prompt. Yes it will print out the password on input :) */ +static int ssh_password_callback(char **ssh_password) +{ + ssize_t i; + char *pass = malloc(512); + + if (!pass) + return -ENOMEM; + + fprintf(stdout, "Host asks for password:\n"); + + i = read(STDIN_FILENO, pass, 512); + if (i > 0) { + pass[i-1] = '\0'; + i = 0; + } else if (i == 0) { /* EOF */ + *pass = '\0'; + i = -1; + } + + if (!i) + *ssh_password = pass; + else + free(pass); + + return i; +} + +static int open_by_token(const char *device, const char *name) +{ + struct crypt_device *cd = NULL; + int r; + + r = crypt_token_register(&SSHTEST_token); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + r = crypt_activate_by_token(cd, name, TOKEN_NUM, ssh_password_callback, 0); + + crypt_free(cd); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static void keyslot_help(void) +{ + printf("Use parameters:\n add device server user path\n" + " open device name\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + crypt_set_debug_level(CRYPT_LOG_DEBUG); + + /* Adding slot to device */ + if (argc == 6 && !strcmp("add", argv[1])) + return token_add(argv[2], argv[3], argv[4], argv[5]); + + /* Key check without activation */ + if (argc == 3 && !strcmp("open", argv[1])) + return open_by_token(argv[2], NULL); + + /* Key check with activation (requires root) */ + if (argc == 4 && !strcmp("open", argv[1])) + return open_by_token(argv[2], argv[3]); + + keyslot_help(); + return 1; +} diff --git a/misc/luks2_keyslot_example/keyslot_test_remote_pass.c b/misc/luks2_keyslot_example/keyslot_test_remote_pass.c new file mode 100644 index 0000000..5aa9500 --- /dev/null +++ b/misc/luks2_keyslot_example/keyslot_test_remote_pass.c @@ -0,0 +1,264 @@ +/* + * Example of LUKS2 token storing third party metadata (EXAMPLE) + * + * Copyright (C) 2016-2021 Milan Broz <gmazyland@gmail.com> + * + * Use: + * - generate LUKS device + * - store passphrase used in previous step remotely (single line w/o \n\r) + * - add new token using this example + * - activate device with passphrase recovered remotely using the example + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <json-c/json.h> +#include <libssh/libssh.h> +#include <libssh/sftp.h> +#include "libcryptsetup.h" + +#define TOKEN_NUM 0 +#define TOKEN_TYPE "sshkeytest" + +#define PASSWORD_LENGTH 8192 + +static json_object *get_token_jobj(struct crypt_device *cd, int token) +{ + const char *json_slot; + + if (crypt_token_json_get(cd, token, &json_slot)) + return NULL; + + return json_tokener_parse(json_slot); +} + +static int read_remote_passphrase(struct crypt_device *cd, const char *host, + const char *user, const char *path, + char *password, size_t password_size) +{ + ssh_session ssh = NULL; + sftp_session sftp = NULL; + sftp_file file = NULL; + ssh_key pkey = NULL; + + int r, port = 22; + + ssh = ssh_new(); + if (!ssh) + return -EINVAL; + + ssh_options_set(ssh, SSH_OPTIONS_HOST, host); + ssh_options_set(ssh, SSH_OPTIONS_USER, user); + ssh_options_set(ssh, SSH_OPTIONS_PORT, &port); + + r = ssh_connect(ssh); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Connection failed: "); + goto out; + } + + r = ssh_is_server_known(ssh); + if (r != SSH_SERVER_KNOWN_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Server not known: "); + r = SSH_AUTH_ERROR; + goto out; + } + + r = ssh_pki_import_privkey_file("/home/user/.ssh/id_rsa", NULL, NULL, NULL, &pkey); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "error\n"); + r = SSH_AUTH_ERROR; + goto out; + } + + r = ssh_userauth_publickey(ssh, user, pkey); + /* or r = ssh_userauth_publickey_auto(ssh, user, NULL); */ + if (r != SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_ERROR, "Public key authentication error: "); + goto out; + } + + sftp = sftp_new(ssh); + if (!sftp) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_init(sftp); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot init sftp session: "); + goto out; + } + + file = sftp_open(sftp, path, O_RDONLY, 0); + if (!file) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_read(file, password, password_size); + if (r < 1 || (size_t)r >= password_size) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot read remote password: "); + r = SSH_FX_FAILURE; + goto out; + } + + r = SSH_OK; +out: + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + } + + if (pkey) + ssh_key_free(pkey); + + if (file) + sftp_close(file); + if (sftp) + sftp_free(sftp); + ssh_disconnect(ssh); + ssh_free(ssh); + return r == SSH_OK ? 0 : -EINVAL; +} + +static int token_add(const char *device, const char *server, + const char *user, const char *path) +{ + struct crypt_device *cd = NULL; + json_object *jobj = NULL, *jobj_keyslots; + int r; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + jobj = json_object_new_object(); + + /* 'type' is mandatory field */ + json_object_object_add(jobj, "type", json_object_new_string(TOKEN_TYPE)); + + /* 'keyslots' is mandatory field (may be empty) */ + jobj_keyslots = json_object_new_array(); + json_object_array_add(jobj_keyslots, json_object_new_string("0")); + json_object_array_add(jobj_keyslots, json_object_new_string("1")); + json_object_object_add(jobj, "keyslots", jobj_keyslots); + + /* third party values */ + json_object_object_add(jobj, "ssh_server", json_object_new_string(server)); + json_object_object_add(jobj, "ssh_user", json_object_new_string(user)); + json_object_object_add(jobj, "ssh_path", json_object_new_string(path)); + + r = crypt_token_json_set(cd, TOKEN_NUM, json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN)); + + crypt_free(cd); + json_object_put(jobj); + + return EXIT_SUCCESS; +} + +static int download_remote_password(struct crypt_device *cd, char *password, size_t password_len) +{ + json_object *jobj_server, *jobj_user, *jobj_path, *jobj_keyslot; + + /* get token json object representation as string */ + jobj_keyslot = get_token_jobj(cd, TOKEN_NUM); + if (!jobj_keyslot) + return -EINVAL; + + + /* extract third party metadata necessary to extract passphrase remotely */ + json_object_object_get_ex(jobj_keyslot, "ssh_server", &jobj_server); + json_object_object_get_ex(jobj_keyslot, "ssh_user", &jobj_user); + json_object_object_get_ex(jobj_keyslot, "ssh_path", &jobj_path); + + return read_remote_passphrase(cd, json_object_get_string(jobj_server), + json_object_get_string(jobj_user), + json_object_get_string(jobj_path), + password, password_len); +} + +static int open_by_remote_password(const char *device, const char *name) +{ + char password[PASSWORD_LENGTH+1]; + struct crypt_device *cd = NULL; + int r; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + /* custom routines to acquire password */ + r = download_remote_password(cd, password, sizeof(password)); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + password[PASSWORD_LENGTH] = '\0'; + + /* open first genuine LUKS2 keyslot available provided the password matches */ + /* for the sake of simplicity password is a string */ + r = crypt_activate_by_passphrase(cd, name, CRYPT_ANY_SLOT, password, strlen(password), 0); + + crypt_free(cd); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static void keyslot_help(void) +{ + printf("Use parameters:\n add device server user path\n" + " open device name\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + crypt_set_debug_level(CRYPT_LOG_DEBUG); + + /* Adding slot to device */ + if (argc == 6 && !strcmp("add", argv[1])) + return token_add(argv[2], argv[3], argv[4], argv[5]); + + /* Password check without activation */ + if (argc == 3 && !strcmp("open", argv[1])) + return open_by_remote_password(argv[2], NULL); + + /* Password check with activation (requires root) */ + if (argc == 4 && !strcmp("open", argv[1])) + return open_by_remote_password(argv[2], argv[3]); + + keyslot_help(); + return 1; +} |