From 89b31054937f90ea0f6954b8b4223dadbc2076fd Mon Sep 17 00:00:00 2001 From: Milan Broz Date: Mon, 24 Jun 2019 09:26:33 +0200 Subject: [PATCH] Add example SSH token handler. Provides example of loadable token handler for activation json validation and metadata dump. For creating new ssh example token use special cryptsetup-ssh binary. --- Makefile.am | 3 +- configure.ac | 9 + misc/luks2_keyslot_example/keyslot_test.c | 12 +- tokens/Makemodule.am | 17 ++ tokens/libcryptsetup-token.sym | 9 + tokens/ssh/cryptsetup-ssh.c | 122 ++++++++ tokens/ssh/libcryptsetup-token-ssh.c | 336 ++++++++++++++++++++++ 7 files changed, 501 insertions(+), 7 deletions(-) create mode 100644 tokens/Makemodule.am create mode 100644 tokens/libcryptsetup-token.sym create mode 100644 tokens/ssh/cryptsetup-ssh.c create mode 100644 tokens/ssh/libcryptsetup-token-ssh.c diff --git a/Makefile.am b/Makefile.am index 49dcda77..b8d2bb6c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -EXTRA_DIST = COPYING.LGPL FAQ docs misc +EXTRA_DIST = COPYING.LGPL FAQ docs misc tokens SUBDIRS = po tests CLEANFILES = DISTCLEAN_TARGETS = @@ -38,6 +38,7 @@ include lib/crypto_backend/Makemodule.am include lib/Makemodule.am include src/Makemodule.am +include tokens/Makemodule.am ACLOCAL_AMFLAGS = -I m4 diff --git a/configure.ac b/configure.ac index e988572e..6b76d1b3 100644 --- a/configure.ac +++ b/configure.ac @@ -347,6 +347,10 @@ AC_ARG_ENABLE([udev], AS_HELP_STRING([--disable-udev], [disable udev support]), [], [enable_udev=yes]) +AC_ARG_ENABLE([ssh-token], + AS_HELP_STRING([--enable-ssh-token], [enable build of ssh-token])) +AM_CONDITIONAL(SSHPLUGIN_TOKEN, test "x$enable_ssh_token" = "xyes") + dnl Try to use pkg-config for devmapper, but fallback to old detection PKG_CHECK_MODULES([DEVMAPPER], [devmapper >= 1.02.03],, [ AC_CHECK_LIB(devmapper, dm_task_set_name,, @@ -380,6 +384,11 @@ PKG_CHECK_MODULES([JSON_C], [json-c]) AC_CHECK_DECLS([json_object_object_add_ex], [], [], [#include ]) AC_CHECK_DECLS([json_object_deep_copy], [], [], [#include ]) +dnl Check for libssh for SSH plugin +if test "x$enable_ssh_token" = "xyes"; then + PKG_CHECK_MODULES([LIBSSH], [libssh]) +fi + dnl Crypto backend configuration. AC_ARG_WITH([crypto_backend], AS_HELP_STRING([--with-crypto_backend=BACKEND], [crypto backend (gcrypt/openssl/nss/kernel/nettle) [openssl]]), diff --git a/misc/luks2_keyslot_example/keyslot_test.c b/misc/luks2_keyslot_example/keyslot_test.c index 5b8cc9a2..b7f1bb74 100644 --- a/misc/luks2_keyslot_example/keyslot_test.c +++ b/misc/luks2_keyslot_example/keyslot_test.c @@ -242,7 +242,7 @@ static int _password_auth(struct crypt_device *cd, ssh_session ssh, password_cb_ return r; } -static int SSHTEST_token_open(struct crypt_device *cd, +static int SSHPLUGIN_token_open(struct crypt_device *cd, int token, char **password, size_t *password_len, @@ -280,9 +280,9 @@ static int SSHTEST_token_open(struct crypt_device *cd, return r ? -EINVAL : r; } -const crypt_token_handler SSHTEST_token = { +const crypt_token_handler SSHPLUGIN_token = { .name = "sshkeytest", - .open = SSHTEST_token_open, + .open = SSHPLUGIN_token_open, }; static int token_add(const char *device, const char *server, @@ -292,7 +292,7 @@ static int token_add(const char *device, const char *server, json_object *jobj = NULL, *jobj_keyslots; int r; - r = crypt_token_register(&SSHTEST_token); + r = crypt_token_register(&SSHPLUGIN_token); if (r < 0) return EXIT_FAILURE; @@ -307,7 +307,7 @@ static int token_add(const char *device, const char *server, } jobj = json_object_new_object(); - json_object_object_add(jobj, "type", json_object_new_string(SSHTEST_token.name)); /* mandatory */ + json_object_object_add(jobj, "type", json_object_new_string(SSHPLUGIN_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 */ @@ -361,7 +361,7 @@ static int open_by_token(const char *device, const char *name) struct crypt_device *cd = NULL; int r; - r = crypt_token_register(&SSHTEST_token); + r = crypt_token_register(&SSHPLUGIN_token); if (r < 0) return EXIT_FAILURE; diff --git a/tokens/Makemodule.am b/tokens/Makemodule.am new file mode 100644 index 00000000..983a6db6 --- /dev/null +++ b/tokens/Makemodule.am @@ -0,0 +1,17 @@ +EXTRA_DIST += tokens/libcryptsetup-token.sym + +TOKENS_LDFLAGS = $(AM_LDFLAGS) -no-undefined \ + -Wl,--version-script=$(top_srcdir)/tokens/libcryptsetup-token.sym \ + -version-info 0:0:0 + +if SSHPLUGIN_TOKEN +libcryptsetup_token_ssh_la_LDFLAGS = $(TOKENS_LDFLAGS) +libcryptsetup_token_ssh_la_SOURCES = tokens/ssh/libcryptsetup-token-ssh.c +libcryptsetup_token_ssh_la_LIBADD = -lssh libcryptsetup.la @JSON_C_LIBS@ +lib_LTLIBRARIES += libcryptsetup-token-ssh.la + +cryptsetup_ssh_SOURCES = tokens/ssh/cryptsetup-ssh.c +cryptsetup_ssh_LDADD = libcryptsetup.la @JSON_C_LIBS@ + +sbin_PROGRAMS += cryptsetup-ssh +endif diff --git a/tokens/libcryptsetup-token.sym b/tokens/libcryptsetup-token.sym new file mode 100644 index 00000000..17ec5991 --- /dev/null +++ b/tokens/libcryptsetup-token.sym @@ -0,0 +1,9 @@ +CRYPTSETUP_TOKEN_1.0 { + global: cryptsetup_token_open; + cryptsetup_token_open_pin; + cryptsetup_token_buffer_free; + cryptsetup_token_validate; + cryptsetup_token_dump; + cryptsetup_token_version; + local: *; +}; diff --git a/tokens/ssh/cryptsetup-ssh.c b/tokens/ssh/cryptsetup-ssh.c new file mode 100644 index 00000000..0011d95a --- /dev/null +++ b/tokens/ssh/cryptsetup-ssh.c @@ -0,0 +1,122 @@ +/* + * Example of LUKS2 token storing third party metadata (EXAMPLE) + * + * Copyright (C) 2016-2020 Milan Broz + * + * Use: + * - generate ssh example 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 +#include +#include +#include +#include +#include +#include "libcryptsetup.h" + +#define TOKEN_NAME "ssh" + +#define PASSWORD_LENGTH 8192 + +#define l_err(cd, x...) crypt_logf(cd, CRYPT_LOG_ERROR, x) +#define l_dbg(cd, x...) crypt_logf(cd, CRYPT_LOG_DEBUG, x) + +static int token_add( + const char *device, + const char *server, + const char *user, + const char *path, + const char *keypath) + +{ + struct crypt_device *cd; + json_object *jobj = NULL; + json_object *jobj_keyslots = NULL; + const char *string_token; + int r, token; + + r = crypt_init(&cd, device); + if (r) + return r; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r) + goto out; + + r = -EINVAL; + jobj = json_object_new_object(); + if (!jobj) + goto out; + + /* type is mandatory field in all tokens and must match handler name member */ + json_object_object_add(jobj, "type", json_object_new_string(TOKEN_NAME)); + + jobj_keyslots = json_object_new_array(); + + /* mandatory array field (may be empty and assigned later */ + json_object_object_add(jobj, "keyslots", jobj_keyslots); + + /* 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)); + json_object_object_add(jobj, "ssh_keypath", json_object_new_string(keypath)); + + string_token = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN); + if (!string_token) { + r = -EINVAL; + goto out; + } + + l_dbg(cd, "Token JSON: %s", string_token); + + r = crypt_token_json_set(cd, CRYPT_ANY_TOKEN, string_token); + if (r < 0) { + l_err(cd, "Failed to write ssh token json."); + goto out; + } + + token = r; + r = crypt_token_assign_keyslot(cd, token, CRYPT_ANY_SLOT); + if (r != token) { + crypt_token_json_set(cd, token, NULL); + r = -EINVAL; + } +out: + json_object_put(jobj); + crypt_free(cd); + return r; +} + +static void token_help(void) +{ + printf("Use parameters:\n add device server user path keypath\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], argv[6]); + + token_help(); + return EXIT_FAILURE; +} diff --git a/tokens/ssh/libcryptsetup-token-ssh.c b/tokens/ssh/libcryptsetup-token-ssh.c new file mode 100644 index 00000000..dd71dc1c --- /dev/null +++ b/tokens/ssh/libcryptsetup-token-ssh.c @@ -0,0 +1,336 @@ +/* + * Example of LUKS2 ssh token handler + * + * Copyright (C) 2016-2020 Milan Broz + * Copyright (C) 2020 Vojtech Trefny + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "libcryptsetup.h" + +#define PASSWORD_LENGTH 8192 + +#define TOKEN_NAME "ssh" +#define TOKEN_VERSION_MAJOR "1" +#define TOKEN_VERSION_MINOR "0" + +#define SERVER_ARG "plugin-ssh-server" +#define USER_ARG "plugin-ssh-user" +#define PATH_ARG "plugin-ssh-path" +#define KEYPATH_ARG "plugin-ssh-keypath" + +#define l_dbg(cd, x...) crypt_logf(cd, CRYPT_LOG_DEBUG, x) + +struct sshplugin_context { + const char *server; + const char *user; + const char *path; + const char *sshkey_path; + + int token; + int keyslot; + + uint8_t status; + + struct crypt_cli *cli; +}; + +const char *cryptsetup_token_version(void) +{ + return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR; +} + +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 sshplugin_download_password(struct crypt_device *cd, ssh_session ssh, + const char *path, char **password, size_t *password_len) +{ + char *pass = NULL; + 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 sshplugin_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, "SSH token initiating ssh session.\n"); + + r = ssh_connect(ssh); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Connection failed: "); + goto out; + } + + r = ssh_session_is_known_server(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 int sshplugin_public_key_auth(struct crypt_device *cd, ssh_session ssh, const ssh_key pkey) +{ + int r; + + 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_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); + } + + 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; +} + +int cryptsetup_token_open_pin(struct crypt_device *cd, int token, const char *pin, + char **password, size_t *password_len, void *usrptr) +{ + int r; + json_object *jobj_server, *jobj_user, *jobj_path, *jobj_token, *jobj_keypath; + ssh_key pkey; + ssh_session ssh; + + 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); + json_object_object_get_ex(jobj_token, "ssh_keypath",&jobj_keypath); + + r = ssh_pki_import_privkey_file(json_object_get_string(jobj_keypath), pin, NULL, NULL, &pkey); + if (r != SSH_OK) { + if (r == SSH_EOF) { + crypt_log(cd, CRYPT_LOG_ERROR, "Failed to open and import private key.\n"); + return -EINVAL; + } + crypt_log(cd, CRYPT_LOG_ERROR, "Failed to import private key (password protected?).\n"); + return -EAGAIN; + } + + ssh = sshplugin_session_init(cd, json_object_get_string(jobj_server), + json_object_get_string(jobj_user)); + if (!ssh) { + ssh_key_free(pkey); + return -EINVAL; + } + + r = sshplugin_public_key_auth(cd, ssh, pkey); + ssh_key_free(pkey); + + if (r == SSH_AUTH_SUCCESS) + r = sshplugin_download_password(cd, ssh, json_object_get_string(jobj_path), + password, password_len); + + ssh_disconnect(ssh); + ssh_free(ssh); + + return r ? -EINVAL : r; +} + +int cryptsetup_token_open(struct crypt_device *cd, int token, + char **password, size_t *password_len, void *usrptr) +{ + return cryptsetup_token_open_pin(cd, token, NULL, password, password_len, usrptr); +} + +void cryptsetup_token_dump(struct crypt_device *cd, const char *json) +{ + json_object *jobj_token, *jobj_server, *jobj_user, *jobj_path, *jobj_keypath; + char buf[4096]; + + jobj_token = json_tokener_parse(json); + if (!jobj_token) + return; + + 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); + json_object_object_get_ex(jobj_token, "ssh_keypath",&jobj_keypath); + + snprintf(buf, sizeof(buf) - 1, "\tssh_server: %s\n\tssh_user: %s\n" + "\tssh_path: %s\n\tssh_key_path: %s\n", + json_object_get_string(jobj_server), + json_object_get_string(jobj_user), + json_object_get_string(jobj_path), + json_object_get_string(jobj_keypath)); + + crypt_log(cd, CRYPT_LOG_NORMAL, buf); + json_object_put(jobj_token); +} + +int cryptsetup_token_validate(struct crypt_device *cd, const char *json) +{ + enum json_tokener_error jerr; + json_object *jobj_token, *jobj; + int r = -EINVAL; + + jobj_token = json_tokener_parse_verbose(json, &jerr); + if (!jobj_token) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_token, "ssh_server", &jobj) || + !json_object_is_type(jobj, json_type_string)) { + l_dbg(cd, "ssh_server element is missing or not string."); + goto out; + } + + if (!json_object_object_get_ex(jobj_token, "ssh_user", &jobj) || + !json_object_is_type(jobj, json_type_string)) { + l_dbg(cd, "ssh_user element is missing or not string."); + goto out; + } + + if (!json_object_object_get_ex(jobj_token, "ssh_path", &jobj) || + !json_object_is_type(jobj, json_type_string)) { + l_dbg(cd, "ssh_path element is missing or not string."); + goto out; + } + + if (!json_object_object_get_ex(jobj_token, "ssh_keypath", &jobj) || + !json_object_is_type(jobj, json_type_string)) { + l_dbg(cd, "ssh_keypath element is missing or not string."); + goto out; + } + + r = 0; +out: + json_object_put(jobj_token); + return r; +}