├── .gitignore ├── testdata ├── local_client.bin ├── local_server.bin ├── random_client.bin └── random_server.bin ├── initramfs ├── install ├── luksrku-hook └── luksrku-script ├── parsers ├── parser_edit.py ├── parser_server.py └── parser_client.py ├── ChangeLog ├── signals.h ├── signals.c ├── editor.h ├── client.h ├── server.h ├── thread.h ├── luks.h ├── argparse_edit.h ├── blacklist.h ├── exec.h ├── argparse_server.h ├── uuid.h ├── log.h ├── openssl.h ├── argparse_client.h ├── udp.h ├── blacklist.c ├── vault.h ├── Makefile ├── msg.h ├── pgmopts.h ├── vaulted_keydb.h ├── global.h ├── util.h ├── luksrku.c ├── thread.c ├── file_encryption.h ├── luks.c ├── uuid.c ├── log.c ├── udp.c ├── exec.c ├── keydb.h ├── pgmopts.c ├── argparse_edit.c ├── openssl.c ├── vaulted_keydb.c ├── argparse_server.c ├── util.c ├── argparse_client.c ├── README.md ├── vault.c ├── server.c ├── keydb.c ├── client.c └── file_encryption.c /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | *.o 3 | luksrku 4 | __pycache__ 5 | -------------------------------------------------------------------------------- /testdata/local_client.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndoe31415/luksrku/HEAD/testdata/local_client.bin -------------------------------------------------------------------------------- /testdata/local_server.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndoe31415/luksrku/HEAD/testdata/local_server.bin -------------------------------------------------------------------------------- /testdata/random_client.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndoe31415/luksrku/HEAD/testdata/random_client.bin -------------------------------------------------------------------------------- /testdata/random_server.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndoe31415/luksrku/HEAD/testdata/random_server.bin -------------------------------------------------------------------------------- /initramfs/install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # 4 | 5 | install() { 6 | SRC="$1" 7 | DST="$2" 8 | cp "$SRC" "$DST" 9 | chown root:root "$DST" 10 | chmod 755 "$DST" 11 | } 12 | 13 | install luksrku-script /usr/share/initramfs-tools/scripts/local-top/luksrku 14 | install luksrku-hook /usr/share/initramfs-tools/hooks/luksrku 15 | -------------------------------------------------------------------------------- /parsers/parser_edit.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | parser = argparse.ArgumentParser(prog = "luksrku edit", description = "Edits a luksrku key database.", add_help = False) 3 | parser.add_argument("-v", "--verbose", action = "count", default = 0, help = "Increase verbosity. Can be specified multiple times.") 4 | parser.add_argument("filename", metavar = "filename", nargs = "?", type = str, help = "Database file to edit.") 5 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | Summary of changes of v0.02 (2019-10-19) 2 | ======================================== 3 | * Tiny change that shows the version number as a tag instead of just the 4 | commit number in the help page. 5 | 6 | Summary of changes of v0.01 (2019-10-19) 7 | ======================================== 8 | * Initial release. Have been using it daily for three years and it works 9 | nicely, time to actually give it a proper version number. 10 | -------------------------------------------------------------------------------- /initramfs/luksrku-hook: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Initramfs-tools hook script for remote LUKS unlocking 4 | # 5 | # Copyright 2016-2022 Johannes Bauer 6 | # Released under GPLv3 7 | 8 | PREREQ="" 9 | 10 | prereqs() 11 | { 12 | echo "$PREREQ" 13 | } 14 | 15 | case $1 in 16 | prereqs) 17 | prereqs 18 | exit 0 19 | ;; 20 | esac 21 | 22 | . /usr/share/initramfs-tools/hook-functions 23 | 24 | if [ ! -f /etc/luksrku-client.bin ]; then 25 | exit 0 26 | fi 27 | cp /etc/luksrku-client.bin ${DESTDIR}/etc/ 28 | copy_exec /usr/local/sbin/luksrku /sbin 29 | -------------------------------------------------------------------------------- /parsers/parser_server.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | parser = argparse.ArgumentParser(prog = "luksrku server", description = "Starts a luksrku key server.", add_help = False) 3 | parser.add_argument("-p", "--port", metavar = "port", default = 23170, help = "Port that is used for both UDP and TCP communication. Defaults to %(default)d.") 4 | parser.add_argument("-s", "--silent", action = "store_true", help = "Do not answer UDP queries for clients trying to find a key server, only serve key database using TCP.") 5 | parser.add_argument("-v", "--verbose", action = "count", default = 0, help = "Increase verbosity. Can be specified multiple times.") 6 | parser.add_argument("filename", metavar = "filename", help = "Database file to load keys from.") 7 | -------------------------------------------------------------------------------- /signals.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __SIGNALS_H__ 25 | #define __SIGNALS_H__ 26 | 27 | #include 28 | 29 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 30 | bool ignore_signal(int signum); 31 | /*************** AUTO GENERATED SECTION ENDS ***************/ 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /signals.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include "signals.h" 27 | 28 | bool ignore_signal(int signum) { 29 | struct sigaction action = { 30 | .sa_handler = SIG_IGN, 31 | .sa_flags = SA_RESTART, 32 | }; 33 | sigemptyset(&action.sa_mask); 34 | return sigaction(signum, &action, NULL) == 0; 35 | } 36 | -------------------------------------------------------------------------------- /editor.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __EDITOR_H__ 25 | #define __EDITOR_H__ 26 | 27 | #include 28 | #include "pgmopts.h" 29 | 30 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 31 | bool editor_start(const struct pgmopts_edit_t *opts); 32 | /*************** AUTO GENERATED SECTION ENDS ***************/ 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /client.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __CLIENT_H__ 25 | #define __CLIENT_H__ 26 | 27 | #include 28 | #include "pgmopts.h" 29 | 30 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 31 | bool keyclient_start(const struct pgmopts_client_t *opts); 32 | /*************** AUTO GENERATED SECTION ENDS ***************/ 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /server.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __SERVER_H__ 25 | #define __SERVER_H__ 26 | 27 | #include 28 | #include "pgmopts.h" 29 | 30 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 31 | bool keyserver_start(const struct pgmopts_server_t *opts); 32 | /*************** AUTO GENERATED SECTION ENDS ***************/ 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /thread.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __THREAD_H__ 25 | #define __THREAD_H__ 26 | 27 | #include 28 | 29 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 30 | bool pthread_create_detached_thread(void (*thread_function)(void *ctx), const void *ctx, unsigned int ctx_length); 31 | /*************** AUTO GENERATED SECTION ENDS ***************/ 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /luks.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __LUKS_H__ 25 | #define __LUKS_H__ 26 | 27 | #include 28 | #include 29 | 30 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 31 | bool is_luks_device_opened(const char *mapping_name); 32 | bool open_luks_device(const uint8_t *encrypted_device_uuid, const char *mapping_name, const char *passphrase, unsigned int passphrase_length, bool allow_discards); 33 | /*************** AUTO GENERATED SECTION ENDS ***************/ 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /argparse_edit.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was AUTO-GENERATED by pypgmopts. 3 | * 4 | * https://github.com/johndoe31415/pypgmopts 5 | * 6 | * Do not edit it by hand, your changes will be overwritten. 7 | * 8 | * Generated at: 2022-06-18 16:30:46 9 | */ 10 | 11 | #ifndef __ARGPARSE_EDIT_H__ 12 | #define __ARGPARSE_EDIT_H__ 13 | 14 | #include 15 | 16 | #define ARGPARSE_EDIT_DEFAULT_VERBOSE 0 17 | 18 | #define ARGPARSE_EDIT_NO_OPTION 0 19 | #define ARGPARSE_EDIT_POSITIONAL_ARG 1 20 | 21 | enum argparse_edit_option_t { 22 | ARG_EDIT_VERBOSE = 2, 23 | ARG_EDIT_FILENAME = 3, 24 | }; 25 | 26 | typedef void (*argparse_edit_errmsg_callback_t)(const char *errmsg, ...); 27 | typedef void (*argparse_edit_errmsg_option_callback_t)(enum argparse_edit_option_t error_option, const char *errmsg, ...); 28 | typedef bool (*argparse_edit_callback_t)(enum argparse_edit_option_t option, const char *value, argparse_edit_errmsg_callback_t errmsg_callback); 29 | typedef bool (*argparse_edit_plausibilization_callback_t)(argparse_edit_errmsg_option_callback_t errmsg_callback); 30 | 31 | bool argparse_edit_parse(int argc, char **argv, argparse_edit_callback_t argument_callback, argparse_edit_plausibilization_callback_t plausibilization_callback); 32 | void argparse_edit_show_syntax(void); 33 | void argparse_edit_parse_or_quit(int argc, char **argv, argparse_edit_callback_t argument_callback, argparse_edit_plausibilization_callback_t plausibilization_callback); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /blacklist.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __BLACKLIST_H__ 25 | #define __BLACKLIST_H__ 26 | 27 | #include 28 | #include 29 | 30 | #define BLACKLIST_ENTRY_COUNT 32 31 | 32 | struct blacklist_entry_t { 33 | uint32_t ip; 34 | double timeout; 35 | }; 36 | 37 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 38 | void blacklist_ip(uint32_t ip, unsigned int timeout_seconds); 39 | bool is_ip_blacklisted(uint32_t ip); 40 | /*************** AUTO GENERATED SECTION ENDS ***************/ 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /parsers/parser_client.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | parser = argparse.ArgumentParser(prog = "luksrku client", description = "Connects to a luksrku key server and unlocks local LUKS volumes.", add_help = False) 3 | parser.add_argument("-t", "--timeout", metavar = "secs", default = 0, help = "When searching for a keyserver and not all volumes can be unlocked, abort after this period of time, given in seconds. Defaults to infinity. This argument can be specified as a host-based configuration parameter as well; the command-line argument always takes precedence.") 4 | parser.add_argument("-p", "--port", metavar = "port", default = 23170, help = "Port that is used for both UDP and TCP communication. Defaults to %(default)d.") 5 | parser.add_argument("--force-unlock-all", action = "store_true", help = "Force attempting of unlock of all volumes, regardless if they're already unlocked or not. Useful for testing unlocking procedure.") 6 | parser.add_argument("--no-luks", action = "store_true", help = "Do not call LUKS/cryptsetup. Useful for testing unlocking procedure.") 7 | parser.add_argument("-v", "--verbose", action = "count", default = 0, help = "Increase verbosity. Can be specified multiple times.") 8 | parser.add_argument("filename", metavar = "filename", help = "Exported database file to load TLS-PSKs and list of disks from.") 9 | parser.add_argument("hostname", metavar = "hostname", nargs = "?", help = "When hostname is given, auto-searching for suitable servers is disabled and only a connection to the given hostname is attempted.") 10 | -------------------------------------------------------------------------------- /exec.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __EXEC_H__ 25 | #define __EXEC_H__ 26 | 27 | #include 28 | 29 | struct exec_cmd_t { 30 | const char **argv; 31 | bool show_output; 32 | const void *stdin_data; 33 | unsigned int stdin_length; 34 | }; 35 | 36 | struct exec_result_t { 37 | bool success; 38 | int returncode; 39 | }; 40 | 41 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 42 | void argv_dump(const char **argv); 43 | struct exec_result_t exec_command(const struct exec_cmd_t *cmd); 44 | /*************** AUTO GENERATED SECTION ENDS ***************/ 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /argparse_server.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was AUTO-GENERATED by pypgmopts. 3 | * 4 | * https://github.com/johndoe31415/pypgmopts 5 | * 6 | * Do not edit it by hand, your changes will be overwritten. 7 | * 8 | * Generated at: 2022-06-18 16:30:46 9 | */ 10 | 11 | #ifndef __ARGPARSE_SERVER_H__ 12 | #define __ARGPARSE_SERVER_H__ 13 | 14 | #include 15 | 16 | #define ARGPARSE_SERVER_DEFAULT_PORT 23170 17 | #define ARGPARSE_SERVER_DEFAULT_VERBOSE 0 18 | 19 | #define ARGPARSE_SERVER_NO_OPTION 0 20 | #define ARGPARSE_SERVER_POSITIONAL_ARG 1 21 | 22 | enum argparse_server_option_t { 23 | ARG_SERVER_PORT = 2, 24 | ARG_SERVER_SILENT = 3, 25 | ARG_SERVER_VERBOSE = 4, 26 | ARG_SERVER_FILENAME = 5, 27 | }; 28 | 29 | typedef void (*argparse_server_errmsg_callback_t)(const char *errmsg, ...); 30 | typedef void (*argparse_server_errmsg_option_callback_t)(enum argparse_server_option_t error_option, const char *errmsg, ...); 31 | typedef bool (*argparse_server_callback_t)(enum argparse_server_option_t option, const char *value, argparse_server_errmsg_callback_t errmsg_callback); 32 | typedef bool (*argparse_server_plausibilization_callback_t)(argparse_server_errmsg_option_callback_t errmsg_callback); 33 | 34 | bool argparse_server_parse(int argc, char **argv, argparse_server_callback_t argument_callback, argparse_server_plausibilization_callback_t plausibilization_callback); 35 | void argparse_server_show_syntax(void); 36 | void argparse_server_parse_or_quit(int argc, char **argv, argparse_server_callback_t argument_callback, argparse_server_plausibilization_callback_t plausibilization_callback); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /uuid.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __UUID_H__ 25 | #define __UUID_H__ 26 | 27 | #include 28 | #include 29 | 30 | #define ASCII_UUID_CHARACTER_COUNT 36 31 | 32 | /* Already includes zero termination */ 33 | #define ASCII_UUID_BUFSIZE (ASCII_UUID_CHARACTER_COUNT + 1) 34 | 35 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 36 | bool is_valid_uuid(const char *ascii_uuid); 37 | bool parse_uuid(uint8_t *uuid, const char *ascii_uuid); 38 | void sprintf_uuid(char *buffer, const uint8_t *uuid); 39 | void dump_uuid(FILE *f, const uint8_t *uuid); 40 | bool uuid_randomize(uint8_t uuid[static 16]); 41 | /*************** AUTO GENERATED SECTION ENDS ***************/ 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __LOG_H__ 25 | #define __LOG_H__ 26 | 27 | #define LOGLEVEL_DEFAULT LLVL_INFO 28 | 29 | enum loglvl_t { 30 | LLVL_FATAL = 0, 31 | LLVL_ERROR = 1, 32 | LLVL_WARNING = 2, 33 | LLVL_INFO = 3, 34 | LLVL_DEBUG = 4, 35 | LLVL_TRACE = 5, 36 | }; 37 | 38 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 39 | void log_setlvl(enum loglvl_t level); 40 | bool should_log(enum loglvl_t level); 41 | void __attribute__ ((format (printf, 2, 3))) log_msg(enum loglvl_t level, const char *msg, ...); 42 | void log_libc(enum loglvl_t level, const char *msg, ...); 43 | void log_openssl(enum loglvl_t level, const char *msg, ...); 44 | /*************** AUTO GENERATED SECTION ENDS ***************/ 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /openssl.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __OPENSSL_H__ 25 | #define __OPENSSL_H__ 26 | 27 | #include 28 | #include 29 | 30 | struct generic_tls_ctx_t { 31 | SSL_CONF_CTX *conf_ctx; 32 | const SSL_METHOD *method; 33 | SSL_CTX *ctx; 34 | }; 35 | 36 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 37 | bool openssl_init(void); 38 | bool create_generic_tls_context(struct generic_tls_ctx_t *gctx, bool server); 39 | void free_generic_tls_context(struct generic_tls_ctx_t *gctx); 40 | int openssl_tls13_psk_establish_session(SSL *ssl, const uint8_t *psk, unsigned int psk_length, const EVP_MD *cipher_md, SSL_SESSION **new_session); 41 | /*************** AUTO GENERATED SECTION ENDS ***************/ 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /argparse_client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was AUTO-GENERATED by pypgmopts. 3 | * 4 | * https://github.com/johndoe31415/pypgmopts 5 | * 6 | * Do not edit it by hand, your changes will be overwritten. 7 | * 8 | * Generated at: 2022-06-18 16:30:46 9 | */ 10 | 11 | #ifndef __ARGPARSE_CLIENT_H__ 12 | #define __ARGPARSE_CLIENT_H__ 13 | 14 | #include 15 | 16 | #define ARGPARSE_CLIENT_DEFAULT_TIMEOUT 0 17 | #define ARGPARSE_CLIENT_DEFAULT_PORT 23170 18 | #define ARGPARSE_CLIENT_DEFAULT_VERBOSE 0 19 | 20 | #define ARGPARSE_CLIENT_NO_OPTION 0 21 | #define ARGPARSE_CLIENT_POSITIONAL_ARG 1 22 | 23 | enum argparse_client_option_t { 24 | ARG_CLIENT_TIMEOUT = 2, 25 | ARG_CLIENT_PORT = 3, 26 | ARG_CLIENT_FORCE_UNLOCK_ALL = 4, 27 | ARG_CLIENT_NO_LUKS = 5, 28 | ARG_CLIENT_VERBOSE = 6, 29 | ARG_CLIENT_FILENAME = 7, 30 | ARG_CLIENT_HOSTNAME = 8, 31 | }; 32 | 33 | typedef void (*argparse_client_errmsg_callback_t)(const char *errmsg, ...); 34 | typedef void (*argparse_client_errmsg_option_callback_t)(enum argparse_client_option_t error_option, const char *errmsg, ...); 35 | typedef bool (*argparse_client_callback_t)(enum argparse_client_option_t option, const char *value, argparse_client_errmsg_callback_t errmsg_callback); 36 | typedef bool (*argparse_client_plausibilization_callback_t)(argparse_client_errmsg_option_callback_t errmsg_callback); 37 | 38 | bool argparse_client_parse(int argc, char **argv, argparse_client_callback_t argument_callback, argparse_client_plausibilization_callback_t plausibilization_callback); 39 | void argparse_client_show_syntax(void); 40 | void argparse_client_parse_or_quit(int argc, char **argv, argparse_client_callback_t argument_callback, argparse_client_plausibilization_callback_t plausibilization_callback); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /udp.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __UDP_H__ 25 | #define __UDP_H__ 26 | 27 | #include 28 | #include "msg.h" 29 | 30 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 31 | int create_udp_socket(unsigned int listen_port, bool send_broadcast, unsigned int rx_timeout_millis); 32 | bool wait_udp_message(int sd, void *data, unsigned int length, struct sockaddr_in *source); 33 | bool send_udp_message(int sd, struct sockaddr_in *destination, const void *data, unsigned int length, bool is_response); 34 | bool send_udp_broadcast_message(int sd, int port, const void *data, unsigned int length); 35 | bool wait_udp_query(int sd, struct udp_query_t *query, struct sockaddr_in *source); 36 | bool wait_udp_response(int sd, struct udp_response_t *response, struct sockaddr_in *source); 37 | /*************** AUTO GENERATED SECTION ENDS ***************/ 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /blacklist.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include "blacklist.h" 28 | #include "global.h" 29 | #include "util.h" 30 | 31 | static struct blacklist_entry_t blacklist[BLACKLIST_ENTRY_COUNT]; 32 | 33 | static bool blacklist_entry_expired(int index) { 34 | return now() > blacklist[index].timeout; 35 | } 36 | 37 | void blacklist_ip(uint32_t ip, unsigned int timeout_seconds) { 38 | for (int i = 0; i < BLACKLIST_ENTRY_COUNT; i++) { 39 | if (blacklist_entry_expired(i)) { 40 | blacklist[i].ip = ip; 41 | blacklist[i].timeout = now() + timeout_seconds; 42 | return; 43 | } 44 | } 45 | } 46 | 47 | bool is_ip_blacklisted(uint32_t ip) { 48 | for (int i = 0; i < BLACKLIST_ENTRY_COUNT; i++) { 49 | if ((ip == blacklist[i].ip) && (!blacklist_entry_expired(i))) { 50 | return true; 51 | } 52 | } 53 | return false; 54 | } 55 | -------------------------------------------------------------------------------- /vault.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __VAULT_H__ 25 | #define __VAULT_H__ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | struct vault_t { 32 | pthread_mutex_t mutex; 33 | unsigned int reference_count; 34 | void *data; 35 | unsigned int data_length; 36 | uint8_t *source_key; 37 | unsigned int source_key_length; 38 | uint8_t auth_tag[16]; 39 | uint8_t dkey[32]; 40 | uint64_t iv; 41 | unsigned int iteration_cnt; 42 | }; 43 | 44 | #define DEFAULT_SOURCE_KEY_LENGTH_BYTES (1024 * 1024) 45 | 46 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 47 | struct vault_t* vault_init(unsigned int data_length, double target_derivation_time); 48 | bool vault_open(struct vault_t *vault); 49 | bool vault_close(struct vault_t *vault); 50 | void vault_free(struct vault_t *vault); 51 | /*************** AUTO GENERATED SECTION ENDS ***************/ 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /initramfs/luksrku-script: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2016-2025 Johannes Bauer 4 | # Released under GPLv3 5 | 6 | PREREQ="lvm2" 7 | 8 | # 9 | # Standard initramfs preamble 10 | # 11 | prereqs() 12 | { 13 | echo "$PREREQ" 14 | } 15 | 16 | case $1 in 17 | prereqs) 18 | prereqs 19 | exit 0 20 | ;; 21 | esac 22 | 23 | . /scripts/functions 24 | 25 | if [ ! -f /etc/luksrku-client.bin ]; then 26 | exit 0 27 | fi 28 | 29 | LUKSRKU_ARGS="" 30 | for ARGUMENT in $(cat /proc/cmdline); do 31 | if [ "${ARGUMENT#luksrku=}" != "${ARGUMENT}" ]; then 32 | LUKSRKU_ARGS="${ARGUMENT#luksrku=}" 33 | fi 34 | done 35 | 36 | LUKSRKU_OPT_DISABLE="0" 37 | LUKSRKU_OPT_TIMEOUT="" 38 | LUKSRKU_OPT_HOST="" 39 | LUKSRKU_OPT_VERBOSE="0" 40 | 41 | IFS="," 42 | for LUKSRKU_ARG in ${LUKSRKU_ARGS}; do 43 | if [ "$LUKSRKU_ARG" = "off" ]; then 44 | LUKSRKU_OPT_DISABLE="1" 45 | elif [ "${LUKSRKU_ARG#timeout=}" != "${LUKSRKU_ARG}" ]; then 46 | LUKSRKU_OPT_TIMEOUT="${LUKSRKU_ARG#timeout=}" 47 | elif [ "${LUKSRKU_ARG#host=}" != "${LUKSRKU_ARG}" ]; then 48 | LUKSRKU_OPT_HOST="${LUKSRKU_ARG#host=}" 49 | elif [ "${LUKSRKU_ARG#verbose=}" != "${LUKSRKU_ARG}" ]; then 50 | LUKSRKU_OPT_VERBOSE="${LUKSRKU_ARG#verbose=}" 51 | else 52 | echo "Ignoring unrecognized luksrku argument: ${LUKSRKU_ARG}" 53 | fi 54 | done 55 | unset IFS 56 | 57 | if [ "$LUKSRKU_OPT_DISABLE" = "1" ]; then 58 | exit 0 59 | fi 60 | 61 | LUKSRKU_PARAMS="" 62 | if [ "$LUKSRKU_OPT_TIMEOUT" != "" ]; then 63 | LUKSRKU_PARAMS="${LUKSRKU_PARAMS} -t ${LUKSRKU_OPT_TIMEOUT}" 64 | fi 65 | if [ "$LUKSRKU_OPT_VERBOSE" -ge 1 ]; then 66 | LUKSRKU_PARAMS="${LUKSRKU_PARAMS} -v" 67 | fi 68 | if [ "$LUKSRKU_OPT_VERBOSE" -ge 2 ]; then 69 | LUKSRKU_PARAMS="${LUKSRKU_PARAMS} -v" 70 | fi 71 | 72 | configure_networking 73 | /sbin/luksrku client ${LUKSRKU_PARAMS} /etc/luksrku-client.bin ${LUKSRKU_OPT_HOST} 74 | exit 0 75 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean test_s test_c install parsers 2 | all: luksrku 3 | 4 | BUILD_REVISION := $(shell git describe --abbrev=10 --dirty --always --tags) 5 | INSTALL_PREFIX := /usr/local/ 6 | CFLAGS := -Wall -Wextra -Wshadow -Wswitch -Wpointer-arith -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes -Werror=implicit-function-declaration -Werror=format -Wno-unused-parameter 7 | CFLAGS += -O3 -std=c11 -pthread -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=500 -DBUILD_REVISION='"$(BUILD_REVISION)"' 8 | CFLAGS += `pkg-config --cflags openssl` 9 | #CFLAGS += -ggdb3 -DDEBUG -fsanitize=address -fsanitize=undefined -fsanitize=leak 10 | PYPGMOPTS := ../Python/pypgmopts/pypgmopts 11 | 12 | LDFLAGS := `pkg-config --libs openssl` 13 | TEST_PREFIX := local 14 | 15 | OBJS := \ 16 | argparse_client.o \ 17 | argparse_edit.o \ 18 | argparse_server.o \ 19 | blacklist.o \ 20 | client.o \ 21 | editor.o \ 22 | exec.o \ 23 | file_encryption.o \ 24 | keydb.o \ 25 | log.o \ 26 | luks.o \ 27 | luksrku.o \ 28 | openssl.o \ 29 | pgmopts.o \ 30 | server.o \ 31 | signals.o \ 32 | thread.o \ 33 | udp.o \ 34 | util.o \ 35 | uuid.o \ 36 | vaulted_keydb.o \ 37 | vault.o 38 | 39 | parsers: 40 | $(PYPGMOPTS) -n edit parsers/parser_edit.py 41 | $(PYPGMOPTS) -n server parsers/parser_server.py 42 | $(PYPGMOPTS) -n client parsers/parser_client.py 43 | 44 | install: all 45 | strip luksrku 46 | cp luksrku $(INSTALL_PREFIX)sbin/ 47 | chown root:root $(INSTALL_PREFIX)sbin/luksrku 48 | chmod 755 $(INSTALL_PREFIX)sbin/luksrku 49 | 50 | clean: 51 | rm -f $(OBJS) $(OBJS_CFG) luksrku 52 | 53 | test_s: luksrku 54 | ./luksrku server -vv testdata/$(TEST_PREFIX)_server.bin 55 | 56 | test_c: luksrku 57 | ./luksrku client -vv --no-luks testdata/$(TEST_PREFIX)_client.bin 58 | 59 | .c.o: 60 | $(CC) $(CFLAGS) -c -o $@ $< 61 | 62 | luksrku: $(OBJS) 63 | $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) 64 | -------------------------------------------------------------------------------- /msg.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __MSG_H__ 25 | #define __MSG_H__ 26 | 27 | #include 28 | #include "global.h" 29 | 30 | /* Magic is the prefix of announcement packages. It is the MD5SUM over the 31 | * string "luksrku v2". This only changes when the protocol that is spoken 32 | * changes. */ 33 | #define UDP_MESSAGE_MAGIC_SIZE 16 34 | #define UDP_MESSAGE_MAGIC (const uint8_t[UDP_MESSAGE_MAGIC_SIZE]){ 0x46, 0xf2, 0xf6, 0xc6, 0x63, 0x12, 0x2e, 0x00, 0xa0, 0x8a, 0xae, 0x42, 0x0c, 0x51, 0xf5, 0x65 } 35 | 36 | struct udp_query_t { 37 | uint8_t magic[UDP_MESSAGE_MAGIC_SIZE]; 38 | uint8_t host_uuid[16]; 39 | } __attribute__ ((packed)); 40 | 41 | struct udp_response_t { 42 | uint8_t magic[UDP_MESSAGE_MAGIC_SIZE]; 43 | } __attribute__ ((packed)); 44 | 45 | struct msg_t { 46 | uint8_t volume_uuid[16]; 47 | uint8_t luks_passphrase_raw[LUKS_PASSPHRASE_RAW_SIZE_BYTES]; 48 | } __attribute__ ((packed)); 49 | 50 | staticassert(sizeof(struct msg_t) == 16 + LUKS_PASSPHRASE_RAW_SIZE_BYTES); 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /pgmopts.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __PGMOPTS_H__ 25 | #define __PGMOPTS_H__ 26 | 27 | #include 28 | 29 | enum pgmopts_pgm_t { 30 | PGM_EDIT, 31 | PGM_SERVER, 32 | PGM_CLIENT, 33 | }; 34 | 35 | struct pgmopts_edit_t { 36 | const char *filename; 37 | unsigned int verbosity; 38 | }; 39 | 40 | struct pgmopts_server_t { 41 | const char *filename; 42 | unsigned int port; 43 | bool answer_udp_queries; 44 | unsigned int verbosity; 45 | }; 46 | 47 | struct pgmopts_client_t { 48 | const char *filename; 49 | const char *hostname; 50 | unsigned int port; 51 | unsigned int timeout_seconds; 52 | bool force_unlock_all; 53 | bool no_luks; 54 | unsigned int verbosity; 55 | }; 56 | 57 | struct pgmopts_t { 58 | enum pgmopts_pgm_t pgm; 59 | union { 60 | struct pgmopts_edit_t edit; 61 | struct pgmopts_server_t server; 62 | struct pgmopts_client_t client; 63 | }; 64 | }; 65 | 66 | extern const struct pgmopts_t *pgmopts; 67 | 68 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 69 | void parse_pgmopts_or_quit(int argc, char **argv); 70 | /*************** AUTO GENERATED SECTION ENDS ***************/ 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /vaulted_keydb.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __VAULTED_KEYDB_H__ 25 | #define __VAULTED_KEYDB_H__ 26 | 27 | #include "keydb.h" 28 | #include "vault.h" 29 | 30 | struct tls_psk_vault_entry_t { 31 | uint8_t tls_psk[PSK_SIZE_BYTES]; 32 | }; 33 | 34 | struct luks_passphrase_vault_entry_t { 35 | struct { 36 | uint8_t luks_passphrase_raw[LUKS_PASSPHRASE_RAW_SIZE_BYTES]; 37 | } volumes[MAX_VOLUMES_PER_HOST]; 38 | }; 39 | 40 | struct vaulted_keydb_t { 41 | keydb_t *keydb; 42 | struct vault_t *tls_psk_vault; 43 | struct vault_t *luks_passphrase_vault; 44 | }; 45 | 46 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 47 | bool vaulted_keydb_get_tls_psk(struct vaulted_keydb_t *vaulted_keydb, uint8_t dest[PSK_SIZE_BYTES], const host_entry_t *host); 48 | bool vaulted_keydb_get_volume_luks_passphases_raw(struct vaulted_keydb_t *vaulted_keydb, void (*copy_callback)(void *ctx, unsigned int volume_index, const void *source), void *copy_ctx, const host_entry_t *host); 49 | struct vaulted_keydb_t *vaulted_keydb_new(keydb_t *keydb); 50 | void vaulted_keydb_free(struct vaulted_keydb_t *vaulted_keydb); 51 | /*************** AUTO GENERATED SECTION ENDS ***************/ 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /global.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __GLOBAL_H__ 25 | #define __GLOBAL_H__ 26 | 27 | /* Blacklisting timeouts in seconds */ 28 | #define BLACKLIST_TIMEOUT_CLIENT 3600 29 | #define BLACKLIST_TIMEOUT_SERVER 15 30 | 31 | /* Size in bytes of the PSK that is used for TLS */ 32 | #define PSK_SIZE_BYTES 32 33 | 34 | /* How many volumes every host may contain */ 35 | #define MAX_VOLUMES_PER_HOST 8 36 | 37 | /* How long in characters a host name may be */ 38 | #define MAX_HOST_NAME_LENGTH 64 39 | 40 | /* How long in characters a cryptsetup device name mapping may be */ 41 | #define MAX_DEVMAPPER_NAME_LENGTH 64 42 | 43 | /* How long a passphrase is (this is raw binary, not text) */ 44 | #define LUKS_PASSPHRASE_RAW_SIZE_BYTES 32 45 | 46 | /* How long a passphrase is in it's encoded form, storing it as a character array */ 47 | #define LUKS_PASSPHRASE_TEXT_SIZE_BYTES ((((LUKS_PASSPHRASE_RAW_SIZE_BYTES + 2) / 3) * 4) + 1) 48 | 49 | /* Number of characters a user-defined passphrase may be long */ 50 | #define MAX_PASSPHRASE_LENGTH 256 51 | 52 | /* Number of characters a database filename can be long */ 53 | #define MAX_FILENAME_LENGTH 256 54 | 55 | /* In what interval the server should broadcast that it's waiting for unlocking */ 56 | #define WAITING_MESSAGE_BROADCAST_INTERVAL_MILLISECONDS 1000 57 | 58 | #define staticassert(cond) _Static_assert((cond), #cond) 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __UTIL_H__ 25 | #define __UTIL_H__ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #define PRINTF_FORMAT_IP(saddrptr) ((saddrptr)->sin_addr.s_addr >> 0) & 0xff, ((saddrptr)->sin_addr.s_addr >> 8) & 0xff, ((saddrptr)->sin_addr.s_addr >> 16) & 0xff, ((saddrptr)->sin_addr.s_addr >> 24) & 0xff 32 | 33 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 34 | bool query_passphrase(const char *prompt, char *passphrase, unsigned int passphrase_maxsize); 35 | void dump_hex_long(FILE *f, const void *vdata, unsigned int length); 36 | void sprintf_hex(char *dest, const uint8_t *data, unsigned int length); 37 | void dump_hex(FILE *f, const void *vdata, unsigned int length, bool use_ascii); 38 | void dump_hexline(FILE *f, const char *prefix, const void *vdata, unsigned int length, bool use_ascii); 39 | bool is_hex(const char *str, int length); 40 | int parse_hexstr(const char *hexstr, uint8_t *data, int maxlen); 41 | bool truncate_crlf(char *string); 42 | bool buffer_randomize(uint8_t *buffer, unsigned int length); 43 | bool is_zero(const void *data, unsigned int length); 44 | bool array_remove(void *base, unsigned int element_size, unsigned int element_count, unsigned int remove_element_index); 45 | bool ascii_encode(char *dest, unsigned int dest_buffer_size, const uint8_t *source_data, unsigned int source_data_length); 46 | double now(void); 47 | /*************** AUTO GENERATED SECTION ENDS ***************/ 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /luksrku.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "openssl.h" 29 | #include "log.h" 30 | #include "pgmopts.h" 31 | #include "editor.h" 32 | #include "openssl.h" 33 | #include "server.h" 34 | #include "client.h" 35 | 36 | #if OPENSSL_VERSION_NUMBER < 0x010100000 37 | #error "luksrku requires at least OpenSSL v1.1 to work." 38 | #endif 39 | 40 | static int main_edit(const struct pgmopts_edit_t *opts) { 41 | log_setlvl(LOGLEVEL_DEFAULT + opts->verbosity); 42 | return editor_start(opts) ? 0 : 1; 43 | } 44 | 45 | static int main_server(const struct pgmopts_server_t *opts) { 46 | log_setlvl(LOGLEVEL_DEFAULT + opts->verbosity); 47 | return keyserver_start(opts) ? 0 : 1; 48 | } 49 | 50 | static int main_client(const struct pgmopts_client_t *opts) { 51 | log_setlvl(LOGLEVEL_DEFAULT + opts->verbosity); 52 | return keyclient_start(opts) ? 0 : 1; 53 | } 54 | 55 | int main(int argc, char **argv) { 56 | #ifdef DEBUG 57 | fprintf(stderr, "WARNING: This has been compiled in DEBUG mode and uses reduced security.\n"); 58 | #endif 59 | parse_pgmopts_or_quit(argc, argv); 60 | 61 | if (!openssl_init()) { 62 | log_msg(LLVL_FATAL, "Could not initialize OpenSSL."); 63 | exit(EXIT_FAILURE); 64 | } 65 | 66 | switch (pgmopts->pgm) { 67 | case PGM_EDIT: 68 | return main_edit(&pgmopts->edit); 69 | 70 | case PGM_SERVER: 71 | return main_server(&pgmopts->server); 72 | 73 | case PGM_CLIENT: 74 | return main_client(&pgmopts->client); 75 | } 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /thread.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "thread.h" 32 | #include "log.h" 33 | 34 | struct pthread_trampoline_data_t { 35 | void (*thread_function)(void *ctx); 36 | uint8_t ctx[]; 37 | }; 38 | 39 | static void* pthread_trampoline(void *vctx) { 40 | struct pthread_trampoline_data_t *tdata = (struct pthread_trampoline_data_t*)vctx; 41 | tdata->thread_function(tdata->ctx); 42 | free(tdata); 43 | return NULL; 44 | } 45 | 46 | bool pthread_create_detached_thread(void (*thread_function)(void *ctx), const void *ctx, unsigned int ctx_length) { 47 | struct pthread_trampoline_data_t *tdata = calloc(1, sizeof(struct pthread_trampoline_data_t) + ctx_length); 48 | if (!tdata) { 49 | log_libc(LLVL_FATAL, "Failed to allocate trampoline data using calloc(3)"); 50 | return false; 51 | } 52 | tdata->thread_function = thread_function; 53 | memcpy(tdata->ctx, ctx, ctx_length); 54 | 55 | pthread_attr_t attrs; 56 | if (pthread_attr_init(&attrs)) { 57 | log_libc(LLVL_FATAL, "Unable to pthread_attr_init(3)"); 58 | free(tdata); 59 | return false; 60 | } 61 | 62 | if (pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED)) { 63 | log_libc(LLVL_FATAL, "Unable to pthread_attr_setdetachstate(3)"); 64 | free(tdata); 65 | return false; 66 | } 67 | 68 | pthread_t thread; 69 | if (pthread_create(&thread, &attrs, pthread_trampoline, tdata)) { 70 | log_libc(LLVL_FATAL, "Unable to pthread_create(3) a client thread"); 71 | free(tdata); 72 | return false; 73 | } 74 | return true; 75 | } 76 | -------------------------------------------------------------------------------- /file_encryption.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __FILE_ENCRYPTION_H__ 25 | #define __FILE_ENCRYPTION_H__ 26 | 27 | #include 28 | #include 29 | 30 | enum kdf_t { 31 | KDF_SCRYPT_MIN = 1, 32 | KDF_SCRYPT_N17_r8_p1 = KDF_SCRYPT_MIN + 0, 33 | KDF_SCRYPT_N18_r8_p1 = KDF_SCRYPT_MIN + 1, 34 | KDF_SCRYPT_MAX = KDF_SCRYPT_MIN + 1, 35 | 36 | KDF_PBKDF2_MIN = 0x100, 37 | KDF_PBKDF2_SHA256_1000 = KDF_PBKDF2_MIN + 0, /* Deliberately crappy KDF for use with empty passphrases */ 38 | KDF_PBKDF2_MAX = KDF_PBKDF2_MIN + 0, 39 | }; 40 | 41 | #define ENCRYPTED_FILE_DEFAULT_KDF KDF_SCRYPT_N18_r8_p1 42 | #define ENCRYPTED_FILE_SALT_SIZE 16 43 | #define ENCRYPTED_FILE_KEY_SIZE 32 44 | #define ENCRYPTED_FILE_AUTH_TAG_SIZE 16 45 | #define ENCRYPTED_FILE_IV_SIZE 16 46 | 47 | typedef bool (*passphrase_callback_function_t)(char *buffer, unsigned int bufsize); 48 | 49 | struct encrypted_file_t { 50 | uint32_t empty_passphrase; 51 | uint32_t kdf; 52 | uint8_t salt[ENCRYPTED_FILE_SALT_SIZE]; 53 | uint8_t iv[ENCRYPTED_FILE_IV_SIZE]; 54 | uint8_t auth_tag[ENCRYPTED_FILE_AUTH_TAG_SIZE]; 55 | uint8_t ciphertext[]; 56 | }; 57 | 58 | struct decrypted_file_t { 59 | bool success; 60 | unsigned int data_length; 61 | void *data; 62 | }; 63 | 64 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 65 | struct decrypted_file_t read_encrypted_file(const char *filename, passphrase_callback_function_t passphrase_callback); 66 | bool write_encrypted_file(const char *filename, const void *plaintext, unsigned int plaintext_length, const char *passphrase, enum kdf_t kdf); 67 | /*************** AUTO GENERATED SECTION ENDS ***************/ 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /luks.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "luks.h" 34 | #include "log.h" 35 | #include "exec.h" 36 | #include "util.h" 37 | #include "uuid.h" 38 | 39 | bool is_luks_device_opened(const char *mapping_name) { 40 | struct exec_cmd_t cmd = { 41 | .argv = (const char *[]){ 42 | "dmsetup", 43 | "status", 44 | mapping_name, 45 | NULL, 46 | }, 47 | .show_output = should_log(LLVL_TRACE), 48 | }; 49 | struct exec_result_t runresult = exec_command(&cmd); 50 | return runresult.success && (runresult.returncode == 0); 51 | } 52 | 53 | bool open_luks_device(const uint8_t *encrypted_device_uuid, const char *mapping_name, const char *passphrase, unsigned int passphrase_length, bool allow_discards) { 54 | char encrypted_device[64]; 55 | strcpy(encrypted_device, "UUID="); 56 | sprintf_uuid(encrypted_device + 5, encrypted_device_uuid); 57 | log_msg(LLVL_INFO, "Trying to unlock LUKS mapping %s based on %s", mapping_name, encrypted_device); 58 | 59 | struct exec_cmd_t cmd = { 60 | .argv = !allow_discards ? (const char *[]) { 61 | "cryptsetup", 62 | "luksOpen", 63 | "-T", "1", 64 | encrypted_device, 65 | mapping_name, 66 | NULL, 67 | } : 68 | (const char *[]) { 69 | "cryptsetup", 70 | "--allow-discards", 71 | "luksOpen", 72 | "-T", "1", 73 | encrypted_device, 74 | mapping_name, 75 | NULL, 76 | }, 77 | .stdin_data = passphrase, 78 | .stdin_length = passphrase_length, 79 | .show_output = should_log(LLVL_DEBUG), 80 | }; 81 | 82 | struct exec_result_t runresult = exec_command(&cmd); 83 | return runresult.success && (runresult.returncode == 0); 84 | } 85 | -------------------------------------------------------------------------------- /uuid.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "uuid.h" 29 | #include "util.h" 30 | 31 | bool is_valid_uuid(const char *ascii_uuid) { 32 | // e43fff25-5a01-40e8-b437-80b9d56c19ff 33 | // '-' at offsets 8 13 18 23 34 | if (!ascii_uuid) { 35 | return false; 36 | } 37 | if (strlen(ascii_uuid) != 36) { 38 | return false; 39 | } 40 | if ((ascii_uuid[8] != '-') || (ascii_uuid[13] != '-') || (ascii_uuid[18] != '-') || (ascii_uuid[23] != '-')) { 41 | return false; 42 | } 43 | if (!is_hex(ascii_uuid + 0, 8) || !is_hex(ascii_uuid + 9, 4) || !is_hex(ascii_uuid + 14, 4) || !is_hex(ascii_uuid + 19, 4) || !is_hex(ascii_uuid + 24, 12)) { 44 | return false; 45 | } 46 | return true; 47 | } 48 | 49 | bool parse_uuid(uint8_t *uuid, const char *ascii_uuid) { 50 | if (!is_valid_uuid(ascii_uuid)) { 51 | return false; 52 | } 53 | parse_hexstr(ascii_uuid + 0, uuid + 0, 4); 54 | parse_hexstr(ascii_uuid + 9, uuid + 4, 2); 55 | parse_hexstr(ascii_uuid + 14, uuid + 6, 2); 56 | parse_hexstr(ascii_uuid + 19, uuid + 8, 2); 57 | parse_hexstr(ascii_uuid + 24, uuid + 10, 6); 58 | return true; 59 | } 60 | 61 | void sprintf_uuid(char *buffer, const uint8_t *uuid) { 62 | buffer[0] = 0; 63 | for (int i = 0; i < 16; i++) { 64 | if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) { 65 | buffer += sprintf(buffer, "-"); 66 | } 67 | buffer += sprintf(buffer, "%02x", uuid[i]); 68 | } 69 | } 70 | 71 | void dump_uuid(FILE *f, const uint8_t *uuid) { 72 | char ascii_uuid[40]; 73 | sprintf_uuid(ascii_uuid, uuid); 74 | fprintf(f, "%s", ascii_uuid); 75 | } 76 | 77 | bool uuid_randomize(uint8_t uuid[static 16]) { 78 | if (!buffer_randomize(uuid, 16)) { 79 | return false; 80 | } 81 | uuid[6] = (uuid[6] & (~0xf0)) | 0x40; 82 | uuid[8] = (uuid[8] & (~0xc0)) | 0x80; 83 | return true; 84 | } 85 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "log.h" 32 | #include "util.h" 33 | 34 | static enum loglvl_t current_loglvl = LOGLEVEL_DEFAULT; 35 | static const char *loglvl_names[] = { 36 | [LLVL_FATAL] = "FATAL", 37 | [LLVL_ERROR] = "ERROR", 38 | [LLVL_WARNING] = "WARNING", 39 | [LLVL_INFO] = "INFO", 40 | [LLVL_DEBUG] = "DEBUG", 41 | [LLVL_TRACE] = "TRACE", 42 | }; 43 | 44 | void log_setlvl(enum loglvl_t level) { 45 | current_loglvl = level; 46 | } 47 | 48 | static void log_prefix(enum loglvl_t level) { 49 | fprintf(stderr, "[%c]: ", loglvl_names[level][0]); 50 | } 51 | 52 | static void log_suffix(void) { 53 | fprintf(stderr, "\n"); 54 | } 55 | 56 | bool should_log(enum loglvl_t level) { 57 | return level <= current_loglvl; 58 | } 59 | 60 | void __attribute__ ((format (printf, 2, 3))) log_msg(enum loglvl_t level, const char *msg, ...) { 61 | if (!should_log(level)) { 62 | /* Suppress message */ 63 | return; 64 | } 65 | 66 | log_prefix(level); 67 | va_list vargs; 68 | va_start(vargs, msg); 69 | vfprintf(stderr, msg, vargs); 70 | va_end(vargs); 71 | 72 | log_suffix(); 73 | } 74 | 75 | void log_libc(enum loglvl_t level, const char *msg, ...) { 76 | if (!should_log(level)) { 77 | /* Suppress message */ 78 | return; 79 | } 80 | 81 | int saved_errno = errno; 82 | log_prefix(level); 83 | va_list vargs; 84 | va_start(vargs, msg); 85 | vfprintf(stderr, msg, vargs); 86 | va_end(vargs); 87 | fprintf(stderr, ": %s (%d)", strerror(saved_errno), saved_errno); 88 | log_suffix(); 89 | } 90 | 91 | static int log_openssl_error_callback(const char *msg, size_t len, void *vlvlptr) { 92 | char msgcopy[strlen(msg) + 1]; 93 | strcpy(msgcopy, msg); 94 | truncate_crlf(msgcopy); 95 | 96 | enum loglvl_t* levelptr = (enum loglvl_t*)vlvlptr; 97 | log_msg(*levelptr, "%s", msgcopy); 98 | return 0; 99 | } 100 | 101 | void log_openssl(enum loglvl_t level, const char *msg, ...) { 102 | if (!should_log(level)) { 103 | /* Suppress message */ 104 | return; 105 | } 106 | 107 | log_prefix(level); 108 | fprintf(stderr, "OpenSSL error: "); 109 | va_list vargs; 110 | va_start(vargs, msg); 111 | vfprintf(stderr, msg, vargs); 112 | va_end(vargs); 113 | log_suffix(); 114 | 115 | ERR_print_errors_cb(log_openssl_error_callback, &level); 116 | 117 | #if 0 118 | log_msg(level, ""); 119 | log_msg(level, "OpenSSL error message:"); 120 | ERR_print_errors_fp(stderr); 121 | log_msg(level, "----------------------"); 122 | #endif 123 | } 124 | -------------------------------------------------------------------------------- /udp.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "log.h" 34 | #include "udp.h" 35 | 36 | int create_udp_socket(unsigned int listen_port, bool send_broadcast, unsigned int rx_timeout_millis) { 37 | int sd = socket(AF_INET, SOCK_DGRAM, 0); 38 | if (sd < 0) { 39 | log_libc(LLVL_ERROR, "Unable to create UDP server socket(2)"); 40 | return -1; 41 | } 42 | if (send_broadcast) { 43 | int value = 1; 44 | if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, &value, sizeof(value))) { 45 | log_libc(LLVL_ERROR, "Unable to set UDP socket in broadcast mode using setsockopt(2)"); 46 | close(sd); 47 | return -1; 48 | } 49 | } 50 | 51 | if (listen_port) { 52 | struct sockaddr_in addr = { 53 | .sin_family = AF_INET, 54 | .sin_port = htons(listen_port), 55 | .sin_addr.s_addr = htonl(INADDR_ANY), 56 | }; 57 | if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { 58 | log_libc(LLVL_ERROR, "Unable to bind UDP socket to listen to port %d", listen_port); 59 | close(sd); 60 | return -1; 61 | } 62 | } 63 | if (rx_timeout_millis) { 64 | struct timeval tv = { 65 | .tv_sec = rx_timeout_millis / 1000, 66 | .tv_usec = (rx_timeout_millis % 1000) * 1000, 67 | }; 68 | if (setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { 69 | log_libc(LLVL_ERROR, "Unable to set UDP receive timeout to %u ms.", rx_timeout_millis); 70 | close(sd); 71 | return -1; 72 | } 73 | } 74 | 75 | return sd; 76 | } 77 | bool wait_udp_message(int sd, void *data, unsigned int length, struct sockaddr_in *source) { 78 | socklen_t socklen = sizeof(struct sockaddr_in); 79 | ssize_t rx_bytes = recvfrom(sd,data, length, 0, (struct sockaddr*)source, &socklen); 80 | return rx_bytes == length; 81 | } 82 | 83 | bool send_udp_message(int sd, struct sockaddr_in *destination, const void *data, unsigned int length, bool is_response) { 84 | int flags = is_response ? MSG_CONFIRM : 0; 85 | ssize_t tx_bytes = sendto(sd, data, length, flags, (struct sockaddr*)destination, sizeof(struct sockaddr_in)); 86 | if (tx_bytes < 0) { 87 | log_libc(LLVL_ERROR, "Unable to sendto(2)"); 88 | return false; 89 | } else if (tx_bytes != length) { 90 | log_libc(LLVL_ERROR, "Unable to sendto(2) the complete message, %d bytes sent, but %u requested.", tx_bytes, length); 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | bool send_udp_broadcast_message(int sd, int port, const void *data, unsigned int length) { 97 | struct sockaddr_in destination = { 98 | .sin_family = AF_INET, 99 | .sin_port = htons(port), 100 | .sin_addr.s_addr = htonl(INADDR_BROADCAST), 101 | }; 102 | return send_udp_message(sd, &destination, data, length, false); 103 | } 104 | 105 | bool wait_udp_query(int sd, struct udp_query_t *query, struct sockaddr_in *source) { 106 | bool rx_successful = wait_udp_message(sd, query, sizeof(struct udp_query_t), source); 107 | if (rx_successful) { 108 | /* Also check if the message contains the correct magic */ 109 | if (!memcmp(query->magic, UDP_MESSAGE_MAGIC, UDP_MESSAGE_MAGIC_SIZE)) { 110 | return true; 111 | } 112 | } 113 | return false; 114 | } 115 | 116 | bool wait_udp_response(int sd, struct udp_response_t *response, struct sockaddr_in *source) { 117 | bool rx_successful = wait_udp_message(sd, response, sizeof(struct udp_response_t), source); 118 | if (rx_successful) { 119 | /* Also check if the message contains the correct magic */ 120 | if (!memcmp(response->magic, UDP_MESSAGE_MAGIC, UDP_MESSAGE_MAGIC_SIZE)) { 121 | return true; 122 | } 123 | } 124 | return false; 125 | } 126 | -------------------------------------------------------------------------------- /exec.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "exec.h" 34 | #include "log.h" 35 | 36 | void argv_dump(const char **argv) { 37 | int i = 0; 38 | while (argv[i]) { 39 | printf(" %2d: '%s'\n", i, argv[i]); 40 | i++; 41 | } 42 | } 43 | 44 | static int arg_count(const char **argv) { 45 | int count = 0; 46 | while (*argv) { 47 | count++; 48 | argv++; 49 | } 50 | return count; 51 | } 52 | 53 | static void argv_free(char** argv) { 54 | char **cur = argv; 55 | while (*cur) { 56 | free(*cur); 57 | cur++; 58 | } 59 | free(argv); 60 | } 61 | 62 | static char **argv_dup(const char **argv) { 63 | int argc = arg_count(argv); 64 | char **result = calloc(1, sizeof(char*) * (argc + 1)); 65 | if (!result) { 66 | log_libc(LLVL_ERROR, "malloc(3) failed in argv_dup"); 67 | return NULL; 68 | } 69 | 70 | result[argc - 1] = NULL; 71 | for (int i = 0; i < argc; i++) { 72 | result[i] = strdup(argv[i]); 73 | if (!result[i]) { 74 | log_libc(LLVL_ERROR, "strdup(3) failed in argv_dup"); 75 | argv_free(result); 76 | return NULL; 77 | } 78 | } 79 | return result; 80 | } 81 | 82 | struct exec_result_t exec_command(const struct exec_cmd_t *command) { 83 | char **argvcopy = argv_dup(command->argv); 84 | if (!argvcopy) { 85 | return (struct exec_result_t) { .success = false }; 86 | } 87 | 88 | int pipefd[2]; 89 | if (pipe(pipefd) == -1) { 90 | log_libc(LLVL_ERROR, "Creation of pipe(2) failed trying to execute %s", argvcopy[0]); 91 | argv_free(argvcopy); 92 | return (struct exec_result_t) { .success = false }; 93 | } 94 | const int pipe_read_end = pipefd[0]; 95 | const int pipe_write_end = pipefd[1]; 96 | 97 | pid_t pid = fork(); 98 | if (pid == -1) { 99 | perror("fork"); 100 | argv_free(argvcopy); 101 | return (struct exec_result_t) { .success = false }; 102 | } 103 | if (pid == 0) { 104 | /* Child */ 105 | 106 | close(pipe_write_end); 107 | if (dup2(pipe_read_end, STDIN_FILENO) == -1) { 108 | log_libc(LLVL_ERROR, "Could not dup2(2) stdin while trying to execute %s", argvcopy[0]); 109 | exit(EXIT_FAILURE); 110 | } 111 | 112 | if (!command->show_output) { 113 | /* Shut up the child if user did not request debug output */ 114 | close(STDOUT_FILENO); 115 | close(STDERR_FILENO); 116 | } 117 | execvp(argvcopy[0], argvcopy); 118 | log_libc(LLVL_ERROR, "Execution of %s in forked child process failed execvp(3)", argvcopy[0]); 119 | 120 | /* Exec failed, terminate child with EXIT_FAILURE (parent will catch 121 | * this as the return code) */ 122 | exit(EXIT_FAILURE); 123 | } 124 | 125 | /* Parent process */ 126 | struct exec_result_t runresult = { 127 | .success = true, 128 | }; 129 | close(pipe_read_end); 130 | 131 | if (command->stdin_data && command->stdin_length) { 132 | unsigned int offset = 0; 133 | unsigned int remaining_bytes = command->stdin_length; 134 | const uint8_t *byte_buffer = (const uint8_t*)command->stdin_data; 135 | while (remaining_bytes) { 136 | ssize_t written = write(pipe_write_end, byte_buffer + offset, remaining_bytes); 137 | if (written <= 0) { 138 | log_libc(LLVL_ERROR, "writing to pipe returned %d", written); 139 | runresult.success = false; 140 | } 141 | offset += written; 142 | remaining_bytes -= written; 143 | } 144 | } 145 | close(pipe_write_end); 146 | 147 | int status; 148 | if (waitpid(pid, &status, 0) == (pid_t)-1) { 149 | log_libc(LLVL_ERROR, "exec_command %s failed executing waitpid(2)", argvcopy[0]); 150 | runresult.success = false; 151 | } else { 152 | runresult.returncode = WEXITSTATUS(status); 153 | } 154 | argv_free(argvcopy); 155 | log_msg(LLVL_DEBUG, "Subprocess (PID %d): %s exited with returncode %d", pid, command->argv[0], runresult.returncode); 156 | return runresult; 157 | } 158 | 159 | 160 | -------------------------------------------------------------------------------- /keydb.h: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS volumes using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #ifndef __KEYDB_H__ 25 | #define __KEYDB_H__ 26 | 27 | #include 28 | #include 29 | 30 | #include "file_encryption.h" 31 | #include "global.h" 32 | 33 | #define ALIGNED __attribute__ ((aligned(4))) 34 | 35 | enum volume_flag_t { 36 | VOLUME_FLAG_ALLOW_DISCARDS = (1 << 0), 37 | }; 38 | 39 | /* Unused so far */ 40 | enum host_flag_t { 41 | HOST_FLAG_UNUSED = 0, 42 | }; 43 | 44 | struct keydb_common_header_t { 45 | unsigned int keydb_version; 46 | } ALIGNED; 47 | 48 | struct volume_entry_v2_t { 49 | uint8_t volume_uuid[16]; /* UUID of crypt_LUKS volume */ 50 | char devmapper_name[MAX_DEVMAPPER_NAME_LENGTH]; /* dmsetup name when unlocked. Zero-terminated string. */ 51 | uint8_t luks_passphrase_raw[LUKS_PASSPHRASE_RAW_SIZE_BYTES]; /* LUKS passphrase used to unlock volume; raw byte data */ 52 | } ALIGNED; 53 | 54 | struct host_entry_v2_t { 55 | uint8_t host_uuid[16]; /* Host UUID */ 56 | char host_name[MAX_HOST_NAME_LENGTH]; /* Descriptive name of host */ 57 | uint8_t tls_psk[PSK_SIZE_BYTES]; /* Raw byte data of TLS-PSK that is used */ 58 | unsigned int volume_count; /* Number of volumes of this host */ 59 | struct volume_entry_v2_t volumes[MAX_VOLUMES_PER_HOST]; /* Volumes of this host */ 60 | } ALIGNED; 61 | 62 | struct keydb_v2_t { 63 | struct keydb_common_header_t common; 64 | bool server_database; 65 | unsigned int host_count; 66 | struct host_entry_v2_t hosts[]; 67 | } ALIGNED; 68 | 69 | struct volume_entry_v3_t { 70 | uint8_t volume_uuid[16]; /* UUID of crypt_LUKS volume */ 71 | char devmapper_name[MAX_DEVMAPPER_NAME_LENGTH]; /* dmsetup name when unlocked. Zero-terminated string. */ 72 | uint8_t luks_passphrase_raw[LUKS_PASSPHRASE_RAW_SIZE_BYTES]; /* LUKS passphrase used to unlock volume; raw byte data */ 73 | unsigned int volume_flags; /* Bitset of enum volume_flag_t */ 74 | } ALIGNED; 75 | 76 | struct host_entry_v3_t { 77 | uint8_t host_uuid[16]; /* Host UUID */ 78 | char host_name[MAX_HOST_NAME_LENGTH]; /* Descriptive name of host */ 79 | uint8_t tls_psk[PSK_SIZE_BYTES]; /* Raw byte data of TLS-PSK that is used */ 80 | unsigned int volume_count; /* Number of volumes of this host */ 81 | unsigned int client_default_timeout_secs; /* Client gives up by default if not everything unlocked after this time */ 82 | unsigned int host_flags; /* Bitset of enum host_flag_t */ 83 | struct volume_entry_v3_t volumes[MAX_VOLUMES_PER_HOST]; /* Volumes of this host */ 84 | } ALIGNED; 85 | 86 | struct keydb_v3_t { 87 | struct keydb_common_header_t common; 88 | bool server_database; 89 | unsigned int host_count; 90 | struct host_entry_v3_t hosts[]; 91 | } ALIGNED; 92 | 93 | 94 | #define KEYDB_CURRENT_VERSION 3 95 | typedef struct volume_entry_v3_t volume_entry_t; 96 | typedef struct host_entry_v3_t host_entry_t; 97 | typedef struct keydb_v3_t keydb_t; 98 | 99 | 100 | /*************** AUTO GENERATED SECTION FOLLOWS ***************/ 101 | keydb_t* keydb_new(void); 102 | keydb_t* keydb_export_public(host_entry_t *host); 103 | void keydb_free(keydb_t *keydb); 104 | volume_entry_t* keydb_get_volume_by_name(host_entry_t *host, const char *devmapper_name); 105 | host_entry_t* keydb_get_host_by_name(keydb_t *keydb, const char *host_name); 106 | const volume_entry_t* keydb_get_volume_by_uuid(const host_entry_t *host, const uint8_t uuid[static 16]); 107 | int keydb_get_host_index(const keydb_t *keydb, const host_entry_t *host); 108 | int keydb_get_volume_index(const host_entry_t *host, const volume_entry_t *volume); 109 | const host_entry_t* keydb_get_host_by_uuid(const keydb_t *keydb, const uint8_t uuid[static 16]); 110 | bool keydb_add_host(keydb_t **keydb, const char *host_name); 111 | bool keydb_del_host_by_name(keydb_t **keydb, const char *host_name); 112 | bool keydb_rekey_host(host_entry_t *host); 113 | volume_entry_t* keydb_add_volume(host_entry_t *host, const char *devmapper_name, const uint8_t volume_uuid[static 16]); 114 | bool keydb_del_volume(host_entry_t *host, const char *devmapper_name); 115 | bool keydb_rekey_volume(volume_entry_t *volume); 116 | bool keydb_volume_passphrase_present(const volume_entry_t *volume); 117 | bool keydb_get_volume_luks_passphrase(const volume_entry_t *volume, char *dest, unsigned int dest_buffer_size); 118 | bool keydb_write(const keydb_t *keydb, const char *filename, const char *passphrase); 119 | keydb_t* keydb_read(const char *filename); 120 | /*************** AUTO GENERATED SECTION ENDS ***************/ 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /pgmopts.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "pgmopts.h" 30 | #include "argparse_edit.h" 31 | #include "argparse_server.h" 32 | #include "argparse_client.h" 33 | 34 | static struct pgmopts_t pgmopts_rw; 35 | const struct pgmopts_t *pgmopts = &pgmopts_rw; 36 | 37 | static void show_syntax(const char *errmsg, int argc, char **argv) { 38 | if (errmsg) { 39 | fprintf(stderr, "error: %s\n", errmsg); 40 | fprintf(stderr, "\n"); 41 | } 42 | fprintf(stderr, "Available commands:\n"); 43 | fprintf(stderr, " %s edit Interactively edit a key database\n", argv[0]); 44 | fprintf(stderr, " %s server Start a key server process\n", argv[0]); 45 | fprintf(stderr, " %s client Unlock LUKS volumes by querying a key server\n", argv[0]); 46 | fprintf(stderr, "\n"); 47 | fprintf(stderr, "For futher help: %s (command) --help\n", argv[0]); 48 | fprintf(stderr, "\n"); 49 | fprintf(stderr, "luksrku version " BUILD_REVISION "\n"); 50 | } 51 | 52 | static bool edit_callback(enum argparse_edit_option_t option, const char *value, argparse_edit_errmsg_callback_t errmsg_callback) { 53 | switch (option) { 54 | case ARG_EDIT_FILENAME: 55 | pgmopts_rw.edit.filename = value; 56 | break; 57 | 58 | case ARG_EDIT_VERBOSE: 59 | pgmopts_rw.edit.verbosity++; 60 | break; 61 | } 62 | return true; 63 | } 64 | 65 | static bool server_callback(enum argparse_server_option_t option, const char *value, argparse_server_errmsg_callback_t errmsg_callback) { 66 | switch (option) { 67 | case ARG_SERVER_FILENAME: 68 | pgmopts_rw.server.filename = value; 69 | break; 70 | 71 | case ARG_SERVER_PORT: 72 | pgmopts_rw.server.port = atoi(value); 73 | break; 74 | 75 | case ARG_SERVER_SILENT: 76 | pgmopts_rw.server.answer_udp_queries = false; 77 | break; 78 | 79 | case ARG_SERVER_VERBOSE: 80 | pgmopts_rw.server.verbosity++; 81 | break; 82 | } 83 | return true; 84 | } 85 | 86 | static bool client_callback(enum argparse_client_option_t option, const char *value, argparse_client_errmsg_callback_t errmsg_callback) { 87 | switch (option) { 88 | case ARG_CLIENT_FILENAME: 89 | pgmopts_rw.client.filename = value; 90 | break; 91 | 92 | case ARG_CLIENT_HOSTNAME: 93 | pgmopts_rw.client.hostname = value; 94 | break; 95 | 96 | case ARG_CLIENT_PORT: 97 | pgmopts_rw.client.port = atoi(value); 98 | break; 99 | 100 | case ARG_CLIENT_TIMEOUT: 101 | pgmopts_rw.client.timeout_seconds = atoi(value); 102 | break; 103 | 104 | case ARG_CLIENT_FORCE_UNLOCK_ALL: 105 | pgmopts_rw.client.force_unlock_all = true; 106 | break; 107 | 108 | case ARG_CLIENT_NO_LUKS: 109 | pgmopts_rw.client.no_luks = true; 110 | break; 111 | 112 | case ARG_CLIENT_VERBOSE: 113 | pgmopts_rw.client.verbosity++; 114 | break; 115 | } 116 | return true; 117 | } 118 | 119 | static void parse_pgmopts_edit(int argc, char **argv) { 120 | pgmopts_rw.edit = (struct pgmopts_edit_t){ 121 | .verbosity = ARGPARSE_EDIT_DEFAULT_VERBOSE, 122 | }; 123 | argparse_edit_parse_or_quit(argc - 1, argv + 1, edit_callback, NULL); 124 | } 125 | 126 | static void parse_pgmopts_server(int argc, char **argv) { 127 | pgmopts_rw.server = (struct pgmopts_server_t){ 128 | .port = ARGPARSE_SERVER_DEFAULT_PORT, 129 | .verbosity = ARGPARSE_SERVER_DEFAULT_VERBOSE, 130 | .answer_udp_queries = true, 131 | }; 132 | argparse_server_parse_or_quit(argc - 1, argv + 1, server_callback, NULL); 133 | } 134 | 135 | static void parse_pgmopts_client(int argc, char **argv) { 136 | pgmopts_rw.client = (struct pgmopts_client_t){ 137 | .timeout_seconds = ARGPARSE_CLIENT_DEFAULT_TIMEOUT, 138 | .port = ARGPARSE_SERVER_DEFAULT_PORT, 139 | .verbosity = ARGPARSE_SERVER_DEFAULT_VERBOSE, 140 | }; 141 | argparse_client_parse_or_quit(argc - 1, argv + 1, client_callback, NULL); 142 | } 143 | 144 | void parse_pgmopts_or_quit(int argc, char **argv) { 145 | if (argc < 2) { 146 | show_syntax("no command supplied", argc, argv); 147 | exit(EXIT_FAILURE); 148 | } 149 | 150 | const char *command = argv[1]; 151 | if (!strcasecmp(command, "edit")) { 152 | pgmopts_rw.pgm = PGM_EDIT; 153 | parse_pgmopts_edit(argc, argv); 154 | } else if (!strcasecmp(command, "server")) { 155 | pgmopts_rw.pgm = PGM_SERVER; 156 | parse_pgmopts_server(argc, argv); 157 | } else if (!strcasecmp(command, "client")) { 158 | pgmopts_rw.pgm = PGM_CLIENT; 159 | parse_pgmopts_client(argc, argv); 160 | } else { 161 | show_syntax("unsupported command supplied", argc, argv); 162 | exit(EXIT_FAILURE); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /argparse_edit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was AUTO-GENERATED by pypgmopts. 3 | * 4 | * https://github.com/johndoe31415/pypgmopts 5 | * 6 | * Do not edit it by hand, your changes will be overwritten. 7 | * 8 | * Generated at: 2022-06-18 16:30:46 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "argparse_edit.h" 20 | 21 | static enum argparse_edit_option_t last_parsed_option; 22 | static char last_error_message[256]; 23 | static const char *option_texts[] = { 24 | [ARG_EDIT_VERBOSE] = "-v / --verbose", 25 | [ARG_EDIT_FILENAME] = "filename", 26 | }; 27 | 28 | enum argparse_edit_option_internal_t { 29 | ARG_EDIT_VERBOSE_SHORT = 'v', 30 | ARG_EDIT_VERBOSE_LONG = 1000, 31 | ARG_EDIT_FILENAME_LONG = 1001, 32 | }; 33 | 34 | static void errmsg_callback(const char *errmsg, ...) { 35 | va_list ap; 36 | va_start(ap, errmsg); 37 | vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap); 38 | va_end(ap); 39 | } 40 | 41 | static void errmsg_option_callback(enum argparse_edit_option_t error_option, const char *errmsg, ...) { 42 | last_parsed_option = error_option; 43 | 44 | va_list ap; 45 | va_start(ap, errmsg); 46 | vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap); 47 | va_end(ap); 48 | } 49 | 50 | bool argparse_edit_parse(int argc, char **argv, argparse_edit_callback_t argument_callback, argparse_edit_plausibilization_callback_t plausibilization_callback) { 51 | last_parsed_option = ARGPARSE_EDIT_NO_OPTION; 52 | const char *short_options = "v"; 53 | struct option long_options[] = { 54 | { "verbose", no_argument, 0, ARG_EDIT_VERBOSE_LONG }, 55 | { "filename", required_argument, 0, ARG_EDIT_FILENAME_LONG }, 56 | { 0 } 57 | }; 58 | 59 | while (true) { 60 | int optval = getopt_long(argc, argv, short_options, long_options, NULL); 61 | if (optval == -1) { 62 | break; 63 | } 64 | last_error_message[0] = 0; 65 | enum argparse_edit_option_internal_t arg = (enum argparse_edit_option_internal_t)optval; 66 | switch (arg) { 67 | case ARG_EDIT_VERBOSE_SHORT: 68 | case ARG_EDIT_VERBOSE_LONG: 69 | last_parsed_option = ARG_EDIT_VERBOSE; 70 | if (!argument_callback(ARG_EDIT_VERBOSE, optarg, errmsg_callback)) { 71 | return false; 72 | } 73 | break; 74 | 75 | default: 76 | last_parsed_option = ARGPARSE_EDIT_NO_OPTION; 77 | errmsg_callback("unrecognized option supplied"); 78 | return false; 79 | } 80 | } 81 | 82 | const int positional_argument_cnt = argc - optind; 83 | const int flexible_positional_args_cnt = positional_argument_cnt - 0; 84 | last_parsed_option = ARGPARSE_EDIT_POSITIONAL_ARG; 85 | if (positional_argument_cnt < 0) { 86 | errmsg_callback("expected a minimum of 0 positional arguments, but %d given.", positional_argument_cnt); 87 | return false; 88 | } 89 | if (positional_argument_cnt > 1) { 90 | errmsg_callback("expected a maximum of 1 positional argument, but %d given.", positional_argument_cnt); 91 | return false; 92 | } 93 | 94 | int positional_index = optind; 95 | last_parsed_option = ARG_EDIT_FILENAME; 96 | for (int i = 0; i < flexible_positional_args_cnt; i++) { 97 | if (!argument_callback(ARG_EDIT_FILENAME, argv[positional_index++], errmsg_callback)) { 98 | return false; 99 | } 100 | } 101 | 102 | if (plausibilization_callback) { 103 | if (!plausibilization_callback(errmsg_option_callback)) { 104 | return false; 105 | } 106 | } 107 | return true; 108 | } 109 | 110 | void argparse_edit_show_syntax(void) { 111 | fprintf(stderr, "usage: luksrku edit [-v] [filename]\n"); 112 | fprintf(stderr, "\n"); 113 | fprintf(stderr, "Edits a luksrku key database.\n"); 114 | fprintf(stderr, "\n"); 115 | fprintf(stderr, "positional arguments:\n"); 116 | fprintf(stderr, " filename Database file to edit.\n"); 117 | fprintf(stderr, "\n"); 118 | fprintf(stderr, "options:\n"); 119 | fprintf(stderr, " -v, --verbose Increase verbosity. Can be specified multiple times.\n"); 120 | } 121 | 122 | void argparse_edit_parse_or_quit(int argc, char **argv, argparse_edit_callback_t argument_callback, argparse_edit_plausibilization_callback_t plausibilization_callback) { 123 | if (!argparse_edit_parse(argc, argv, argument_callback, plausibilization_callback)) { 124 | if (last_parsed_option > ARGPARSE_EDIT_POSITIONAL_ARG) { 125 | if (last_error_message[0]) { 126 | fprintf(stderr, "luksrku edit: error parsing argument %s -- %s\n", option_texts[last_parsed_option], last_error_message); 127 | } else { 128 | fprintf(stderr, "luksrku edit: error parsing argument %s -- no details available\n", option_texts[last_parsed_option]); 129 | } 130 | } else if (last_parsed_option == ARGPARSE_EDIT_POSITIONAL_ARG) { 131 | fprintf(stderr, "luksrku edit: error parsing optional arguments -- %s\n", last_error_message); 132 | } 133 | argparse_edit_show_syntax(); 134 | exit(EXIT_FAILURE); 135 | } 136 | } 137 | 138 | #ifdef __ARGPARSE_MAIN__ 139 | /* gcc -D __ARGPARSE_MAIN__ -O2 -Wall -o argparse argparse_edit.c 140 | */ 141 | 142 | static const char *option_enum_to_str(enum argparse_edit_option_t option) { 143 | switch (option) { 144 | case ARG_EDIT_VERBOSE: return "ARG_EDIT_VERBOSE"; 145 | case ARG_EDIT_FILENAME: return "ARG_EDIT_FILENAME"; 146 | } 147 | return "UNKNOWN"; 148 | } 149 | 150 | bool arg_print_callback(enum argparse_edit_option_t option, const char *value, argparse_edit_errmsg_callback_t errmsg_callback) { 151 | fprintf(stderr, "%s = \"%s\"\n", option_enum_to_str(option), value); 152 | return true; 153 | } 154 | 155 | int main(int argc, char **argv) { 156 | argparse_edit_parse_or_quit(argc, argv, arg_print_callback, NULL); 157 | return 0; 158 | } 159 | #endif 160 | -------------------------------------------------------------------------------- /openssl.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "openssl.h" 30 | #include "log.h" 31 | 32 | bool openssl_init(void) { 33 | OpenSSL_add_all_algorithms(); 34 | return true; 35 | } 36 | 37 | bool create_generic_tls_context(struct generic_tls_ctx_t *gctx, bool server) { 38 | memset(gctx, 0, sizeof(struct generic_tls_ctx_t)); 39 | 40 | gctx->conf_ctx = SSL_CONF_CTX_new(); 41 | if (!gctx->conf_ctx) { 42 | log_openssl(LLVL_FATAL, "Cannot initialize TLS generic context config context."); 43 | return false; 44 | } 45 | 46 | if (server) { 47 | gctx->method = TLS_server_method(); 48 | if (!gctx->method) { 49 | log_openssl(LLVL_FATAL, "Cannot initialize TLS server method."); 50 | return false; 51 | } 52 | } else { 53 | gctx->method = TLS_client_method(); 54 | if (!gctx->method) { 55 | log_openssl(LLVL_FATAL, "Cannot initialize TLS client method."); 56 | return false; 57 | } 58 | } 59 | 60 | gctx->ctx = SSL_CTX_new(gctx->method); 61 | if (!gctx->ctx) { 62 | log_openssl(LLVL_FATAL, "Cannot initialize TLS generic context context."); 63 | return false; 64 | } 65 | 66 | /* Disable insecure previous protocol variants */ 67 | /* Disable compression (not secure, should be disabled everywhere) */ 68 | /* Disable DH param reuse (we use ECDH and this doesn't affect us, but in case someone changes to EDH) */ 69 | /* Disable session resumption (unnecessary attack surface) */ 70 | /* Disable resumption on renegotiation (unnecessary attack surface) */ 71 | /* TODO: Disable renegotiation altogether! How? */ 72 | const long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_COMPRESSION | SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; 73 | SSL_CTX_set_options(gctx->ctx, flags); 74 | 75 | if (!SSL_CTX_set_min_proto_version(gctx->ctx, TLS1_3_VERSION)) { 76 | log_openssl(LLVL_FATAL, "Cannot set TLS generic context minimal version."); 77 | return false; 78 | } 79 | 80 | if (!SSL_CTX_set_max_proto_version(gctx->ctx, TLS1_3_VERSION)) { 81 | log_openssl(LLVL_FATAL, "Cannot set TLS generic context maximal version."); 82 | return false; 83 | } 84 | 85 | /* SSL_CTX_set_ciphersuites for TLSv1.3 86 | * SSL_CTX_set_cipher_list for TLS v1.2 and below */ 87 | if (!SSL_CTX_set_ciphersuites(gctx->ctx, "TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384")) { 88 | log_openssl(LLVL_FATAL, "Cannot set TLS generic context cipher suites."); 89 | return false; 90 | } 91 | 92 | if (!SSL_CTX_set1_sigalgs_list(gctx->ctx, "ed448:ed25519")) { 93 | log_openssl(LLVL_FATAL, "Cannot set TLS generic context signature algorithms."); 94 | return false; 95 | } 96 | 97 | if (!SSL_CTX_set1_curves_list(gctx->ctx, "X448:X25519")) { 98 | log_openssl(LLVL_FATAL, "Cannot set TLS generic context ECDHE curves."); 99 | return false; 100 | } 101 | return true; 102 | } 103 | 104 | void free_generic_tls_context(struct generic_tls_ctx_t *gctx) { 105 | SSL_CTX_free(gctx->ctx); 106 | gctx->ctx = NULL; 107 | 108 | SSL_CONF_CTX_free(gctx->conf_ctx); 109 | gctx->conf_ctx = NULL; 110 | } 111 | 112 | enum psk_hash_t { 113 | PSK_HASH_SHA256, 114 | PSK_HASH_SHA384, 115 | }; 116 | 117 | int openssl_tls13_psk_establish_session(SSL *ssl, const uint8_t *psk, unsigned int psk_length, const EVP_MD *cipher_md, SSL_SESSION **new_session) { 118 | uint8_t codepoint[2]; 119 | if (cipher_md == EVP_sha256()) { 120 | // TLS_AES_128_GCM_SHA256 121 | codepoint[0] = 0x13; 122 | codepoint[1] = 0x01; 123 | } else if (cipher_md == EVP_sha384()) { 124 | // TLS_AES_256_GCM_SHA384 125 | codepoint[0] = 0x13; 126 | codepoint[1] = 0x02; 127 | } else { 128 | log_msg(LLVL_ERROR, "Unknown hash function %p (%s) passed for which we do not know how to create a SSL_CIPHER*.", cipher_md, EVP_MD_name(cipher_md)); 129 | return 0; 130 | } 131 | 132 | const SSL_CIPHER *cipher = SSL_CIPHER_find(ssl, codepoint); 133 | if (!cipher) { 134 | log_msg(LLVL_ERROR, "Unable to determine SSL_CIPHER* from codepoint 0x%02x 0x%02x (%s).", codepoint[0], codepoint[1], EVP_MD_name(cipher_md)); 135 | return 0; 136 | } 137 | 138 | SSL_SESSION *sess = SSL_SESSION_new(); 139 | if (!sess) { 140 | log_openssl(LLVL_ERROR, "Failed to create SSL_SESSION context for client."); 141 | return 0; 142 | } 143 | 144 | int return_value = 1; 145 | do { 146 | if (!SSL_SESSION_set1_master_key(sess, psk, psk_length)) { 147 | log_openssl(LLVL_ERROR, "Failed to set TLSv1.3-PSK master key."); 148 | return_value = 0; 149 | break; 150 | } 151 | 152 | if (!SSL_SESSION_set_cipher(sess, cipher)) { 153 | log_openssl(LLVL_ERROR, "Failed to set TLSv1.3-PSK cipher."); 154 | return_value = 0; 155 | break; 156 | } 157 | 158 | if (!SSL_SESSION_set_protocol_version(sess, TLS1_3_VERSION)) { 159 | log_openssl(LLVL_ERROR, "Failed to set TLSv1.3-PSK protocol version."); 160 | return_value = 0; 161 | break; 162 | } 163 | } while (false); 164 | 165 | if (return_value) { 166 | *new_session = sess; 167 | } else { 168 | SSL_SESSION_free(sess); 169 | } 170 | return return_value; 171 | } 172 | -------------------------------------------------------------------------------- /vaulted_keydb.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include "vaulted_keydb.h" 28 | #include "log.h" 29 | 30 | static struct tls_psk_vault_entry_t *vaulted_keydb_get_tls_psk_for_hostindex(struct vaulted_keydb_t *vkeydb, unsigned int host_index) { 31 | return ((struct tls_psk_vault_entry_t*)vkeydb->tls_psk_vault->data) + host_index; 32 | } 33 | 34 | static struct luks_passphrase_vault_entry_t *vaulted_keydb_get_luks_passphrase_for_hostindex(struct vaulted_keydb_t *vkeydb, unsigned int host_index) { 35 | return ((struct luks_passphrase_vault_entry_t*)vkeydb->luks_passphrase_vault->data) + host_index; 36 | } 37 | 38 | static void move_data_into_vault(struct vaulted_keydb_t *dest, keydb_t *src) { 39 | for (unsigned int i = 0; i < src->host_count; i++) { 40 | host_entry_t *host = &src->hosts[i]; 41 | 42 | /* Copy over TLS-PSK and remove original */ 43 | struct tls_psk_vault_entry_t *dest_tls_psk = vaulted_keydb_get_tls_psk_for_hostindex(dest, i); 44 | memcpy(&dest_tls_psk->tls_psk, host->tls_psk, PSK_SIZE_BYTES); 45 | OPENSSL_cleanse(host->tls_psk, PSK_SIZE_BYTES); 46 | 47 | /* Copy over all LUKS keys and remove originals */ 48 | struct luks_passphrase_vault_entry_t *dest_luks_passphrase = vaulted_keydb_get_luks_passphrase_for_hostindex(dest, i); 49 | for (unsigned int j = 0; j < host->volume_count; j++) { 50 | volume_entry_t *volume = &host->volumes[j]; 51 | memcpy(&dest_luks_passphrase->volumes[j].luks_passphrase_raw, volume->luks_passphrase_raw, LUKS_PASSPHRASE_RAW_SIZE_BYTES); 52 | OPENSSL_cleanse(volume->luks_passphrase_raw, LUKS_PASSPHRASE_RAW_SIZE_BYTES); 53 | } 54 | } 55 | } 56 | 57 | bool vaulted_keydb_get_tls_psk(struct vaulted_keydb_t *vaulted_keydb, uint8_t dest[PSK_SIZE_BYTES], const host_entry_t *host) { 58 | int host_index = keydb_get_host_index(vaulted_keydb->keydb, host); 59 | if (host_index < 0) { 60 | log_msg(LLVL_FATAL, "Unable to retrieve host index for vaulted key db entry."); 61 | return false; 62 | } 63 | 64 | /* Get a pointer into the vaulted structure */ 65 | struct tls_psk_vault_entry_t *entry = vaulted_keydb_get_tls_psk_for_hostindex(vaulted_keydb, host_index); 66 | 67 | /* Then decrypt vault */ 68 | if (!vault_open(vaulted_keydb->tls_psk_vault)) { 69 | log_msg(LLVL_FATAL, "Unable to open TLS-PSK vault of vaulted key db entry."); 70 | return false; 71 | } 72 | 73 | /* Copy out the data we need */ 74 | memcpy(dest, &entry->tls_psk, PSK_SIZE_BYTES); 75 | 76 | /* And close it back up */ 77 | if (!vault_close(vaulted_keydb->tls_psk_vault)) { 78 | OPENSSL_cleanse(dest, PSK_SIZE_BYTES); 79 | log_msg(LLVL_FATAL, "Unable to close TLS-PSK vault of vaulted key db entry."); 80 | return false; 81 | } 82 | 83 | return true; 84 | } 85 | 86 | bool vaulted_keydb_get_volume_luks_passphases_raw(struct vaulted_keydb_t *vaulted_keydb, void (*copy_callback)(void *vctx, unsigned int volume_index, const void *source), void *copy_ctx, const host_entry_t *host) { 87 | int host_index = keydb_get_host_index(vaulted_keydb->keydb, host); 88 | if (host_index < 0) { 89 | log_msg(LLVL_FATAL, "Unable to retrieve host index for vaulted key db entry."); 90 | return false; 91 | } 92 | 93 | /* Get a pointer into the vaulted structure */ 94 | struct luks_passphrase_vault_entry_t *entry = vaulted_keydb_get_luks_passphrase_for_hostindex(vaulted_keydb, host_index); 95 | 96 | /* Then decrypt vault */ 97 | if (!vault_open(vaulted_keydb->luks_passphrase_vault)) { 98 | log_msg(LLVL_FATAL, "Unable to open LUKS passphrase vault of vaulted key db entry."); 99 | return false; 100 | } 101 | 102 | /* Copy out the data we need by calling back for all volumes */ 103 | for (unsigned int i = 0; i < host->volume_count; i++) { 104 | copy_callback(copy_ctx, i, &entry->volumes[i].luks_passphrase_raw); 105 | } 106 | 107 | /* And close it back up */ 108 | if (!vault_close(vaulted_keydb->luks_passphrase_vault)) { 109 | log_msg(LLVL_FATAL, "Unable to close LUKS passphrase vault of vaulted key db entry."); 110 | return false; 111 | } 112 | return true; 113 | } 114 | 115 | struct vaulted_keydb_t *vaulted_keydb_new(keydb_t *keydb) { 116 | struct vaulted_keydb_t *vaulted_keydb = calloc(1, sizeof(struct vaulted_keydb_t)); 117 | if (!vaulted_keydb) { 118 | log_msg(LLVL_FATAL, "Unable to calloc(3) vaulted keydb"); 119 | return NULL; 120 | } 121 | 122 | vaulted_keydb->keydb = keydb; 123 | 124 | vaulted_keydb->tls_psk_vault = vault_init(sizeof(struct tls_psk_vault_entry_t) * keydb->host_count, 0.025); 125 | if (!vaulted_keydb->tls_psk_vault) { 126 | log_msg(LLVL_FATAL, "Unable to create TLS-PSK vault"); 127 | vaulted_keydb_free(vaulted_keydb); 128 | return NULL; 129 | } 130 | 131 | vaulted_keydb->luks_passphrase_vault = vault_init(sizeof(struct luks_passphrase_vault_entry_t) * keydb->host_count, 0.025); 132 | if (!vaulted_keydb->luks_passphrase_vault) { 133 | log_msg(LLVL_FATAL, "Unable to create LUKS passphrase vault"); 134 | vaulted_keydb_free(vaulted_keydb); 135 | return NULL; 136 | } 137 | 138 | /* Now move data from the original keydb into the vaulted keydb (erase 139 | * original keys) */ 140 | move_data_into_vault(vaulted_keydb, keydb); 141 | 142 | /* Finally, close the vaults */ 143 | if (!vault_close(vaulted_keydb->tls_psk_vault)) { 144 | log_msg(LLVL_FATAL, "Failed to close TLS-PSK vault"); 145 | vaulted_keydb_free(vaulted_keydb); 146 | return NULL; 147 | } 148 | 149 | if (!vault_close(vaulted_keydb->luks_passphrase_vault)) { 150 | log_msg(LLVL_FATAL, "Failed to close LUKS passhrase vault"); 151 | vaulted_keydb_free(vaulted_keydb); 152 | return NULL; 153 | } 154 | 155 | return vaulted_keydb; 156 | } 157 | 158 | void vaulted_keydb_free(struct vaulted_keydb_t *vaulted_keydb) { 159 | if (!vaulted_keydb) { 160 | return; 161 | } 162 | vault_free(vaulted_keydb->luks_passphrase_vault); 163 | vault_free(vaulted_keydb->tls_psk_vault); 164 | free(vaulted_keydb); 165 | } 166 | -------------------------------------------------------------------------------- /argparse_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was AUTO-GENERATED by pypgmopts. 3 | * 4 | * https://github.com/johndoe31415/pypgmopts 5 | * 6 | * Do not edit it by hand, your changes will be overwritten. 7 | * 8 | * Generated at: 2022-06-18 16:30:46 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "argparse_server.h" 20 | 21 | static enum argparse_server_option_t last_parsed_option; 22 | static char last_error_message[256]; 23 | static const char *option_texts[] = { 24 | [ARG_SERVER_PORT] = "-p / --port", 25 | [ARG_SERVER_SILENT] = "-s / --silent", 26 | [ARG_SERVER_VERBOSE] = "-v / --verbose", 27 | [ARG_SERVER_FILENAME] = "filename", 28 | }; 29 | 30 | enum argparse_server_option_internal_t { 31 | ARG_SERVER_PORT_SHORT = 'p', 32 | ARG_SERVER_SILENT_SHORT = 's', 33 | ARG_SERVER_VERBOSE_SHORT = 'v', 34 | ARG_SERVER_PORT_LONG = 1000, 35 | ARG_SERVER_SILENT_LONG = 1001, 36 | ARG_SERVER_VERBOSE_LONG = 1002, 37 | ARG_SERVER_FILENAME_LONG = 1003, 38 | }; 39 | 40 | static void errmsg_callback(const char *errmsg, ...) { 41 | va_list ap; 42 | va_start(ap, errmsg); 43 | vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap); 44 | va_end(ap); 45 | } 46 | 47 | static void errmsg_option_callback(enum argparse_server_option_t error_option, const char *errmsg, ...) { 48 | last_parsed_option = error_option; 49 | 50 | va_list ap; 51 | va_start(ap, errmsg); 52 | vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap); 53 | va_end(ap); 54 | } 55 | 56 | bool argparse_server_parse(int argc, char **argv, argparse_server_callback_t argument_callback, argparse_server_plausibilization_callback_t plausibilization_callback) { 57 | last_parsed_option = ARGPARSE_SERVER_NO_OPTION; 58 | const char *short_options = "p:sv"; 59 | struct option long_options[] = { 60 | { "port", required_argument, 0, ARG_SERVER_PORT_LONG }, 61 | { "silent", no_argument, 0, ARG_SERVER_SILENT_LONG }, 62 | { "verbose", no_argument, 0, ARG_SERVER_VERBOSE_LONG }, 63 | { "filename", required_argument, 0, ARG_SERVER_FILENAME_LONG }, 64 | { 0 } 65 | }; 66 | 67 | while (true) { 68 | int optval = getopt_long(argc, argv, short_options, long_options, NULL); 69 | if (optval == -1) { 70 | break; 71 | } 72 | last_error_message[0] = 0; 73 | enum argparse_server_option_internal_t arg = (enum argparse_server_option_internal_t)optval; 74 | switch (arg) { 75 | case ARG_SERVER_PORT_SHORT: 76 | case ARG_SERVER_PORT_LONG: 77 | last_parsed_option = ARG_SERVER_PORT; 78 | if (!argument_callback(ARG_SERVER_PORT, optarg, errmsg_callback)) { 79 | return false; 80 | } 81 | break; 82 | 83 | case ARG_SERVER_SILENT_SHORT: 84 | case ARG_SERVER_SILENT_LONG: 85 | last_parsed_option = ARG_SERVER_SILENT; 86 | if (!argument_callback(ARG_SERVER_SILENT, optarg, errmsg_callback)) { 87 | return false; 88 | } 89 | break; 90 | 91 | case ARG_SERVER_VERBOSE_SHORT: 92 | case ARG_SERVER_VERBOSE_LONG: 93 | last_parsed_option = ARG_SERVER_VERBOSE; 94 | if (!argument_callback(ARG_SERVER_VERBOSE, optarg, errmsg_callback)) { 95 | return false; 96 | } 97 | break; 98 | 99 | default: 100 | last_parsed_option = ARGPARSE_SERVER_NO_OPTION; 101 | errmsg_callback("unrecognized option supplied"); 102 | return false; 103 | } 104 | } 105 | 106 | const int positional_argument_cnt = argc - optind; 107 | last_parsed_option = ARGPARSE_SERVER_POSITIONAL_ARG; 108 | if (positional_argument_cnt != 1) { 109 | errmsg_callback("expected exactly 1 positional argument, but %d given.", positional_argument_cnt); 110 | return false; 111 | } 112 | 113 | int positional_index = optind; 114 | last_parsed_option = ARG_SERVER_FILENAME; 115 | if (!argument_callback(ARG_SERVER_FILENAME, argv[positional_index++], errmsg_callback)) { 116 | return false; 117 | } 118 | 119 | if (plausibilization_callback) { 120 | if (!plausibilization_callback(errmsg_option_callback)) { 121 | return false; 122 | } 123 | } 124 | return true; 125 | } 126 | 127 | void argparse_server_show_syntax(void) { 128 | fprintf(stderr, "usage: luksrku server [-p port] [-s] [-v] filename\n"); 129 | fprintf(stderr, "\n"); 130 | fprintf(stderr, "Starts a luksrku key server.\n"); 131 | fprintf(stderr, "\n"); 132 | fprintf(stderr, "positional arguments:\n"); 133 | fprintf(stderr, " filename Database file to load keys from.\n"); 134 | fprintf(stderr, "\n"); 135 | fprintf(stderr, "options:\n"); 136 | fprintf(stderr, " -p port, --port port Port that is used for both UDP and TCP communication. Defaults to 23170.\n"); 137 | fprintf(stderr, " -s, --silent Do not answer UDP queries for clients trying to find a key server, only\n"); 138 | fprintf(stderr, " serve key database using TCP.\n"); 139 | fprintf(stderr, " -v, --verbose Increase verbosity. Can be specified multiple times.\n"); 140 | } 141 | 142 | void argparse_server_parse_or_quit(int argc, char **argv, argparse_server_callback_t argument_callback, argparse_server_plausibilization_callback_t plausibilization_callback) { 143 | if (!argparse_server_parse(argc, argv, argument_callback, plausibilization_callback)) { 144 | if (last_parsed_option > ARGPARSE_SERVER_POSITIONAL_ARG) { 145 | if (last_error_message[0]) { 146 | fprintf(stderr, "luksrku server: error parsing argument %s -- %s\n", option_texts[last_parsed_option], last_error_message); 147 | } else { 148 | fprintf(stderr, "luksrku server: error parsing argument %s -- no details available\n", option_texts[last_parsed_option]); 149 | } 150 | } else if (last_parsed_option == ARGPARSE_SERVER_POSITIONAL_ARG) { 151 | fprintf(stderr, "luksrku server: error parsing optional arguments -- %s\n", last_error_message); 152 | } 153 | argparse_server_show_syntax(); 154 | exit(EXIT_FAILURE); 155 | } 156 | } 157 | 158 | #ifdef __ARGPARSE_MAIN__ 159 | /* gcc -D __ARGPARSE_MAIN__ -O2 -Wall -o argparse argparse_server.c 160 | */ 161 | 162 | static const char *option_enum_to_str(enum argparse_server_option_t option) { 163 | switch (option) { 164 | case ARG_SERVER_PORT: return "ARG_SERVER_PORT"; 165 | case ARG_SERVER_SILENT: return "ARG_SERVER_SILENT"; 166 | case ARG_SERVER_VERBOSE: return "ARG_SERVER_VERBOSE"; 167 | case ARG_SERVER_FILENAME: return "ARG_SERVER_FILENAME"; 168 | } 169 | return "UNKNOWN"; 170 | } 171 | 172 | bool arg_print_callback(enum argparse_server_option_t option, const char *value, argparse_server_errmsg_callback_t errmsg_callback) { 173 | fprintf(stderr, "%s = \"%s\"\n", option_enum_to_str(option), value); 174 | return true; 175 | } 176 | 177 | int main(int argc, char **argv) { 178 | argparse_server_parse_or_quit(argc, argv, arg_print_callback, NULL); 179 | return 0; 180 | } 181 | #endif 182 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "util.h" 31 | #include "log.h" 32 | #include "global.h" 33 | 34 | bool query_passphrase(const char *prompt, char *passphrase, unsigned int passphrase_maxsize) { 35 | if (passphrase_maxsize == 0) { 36 | return false; 37 | } 38 | if (EVP_read_pw_string(passphrase, passphrase_maxsize - 1, prompt, 0) != 0) { 39 | log_openssl(LLVL_ERROR, "EVP_read_pw_string failed"); 40 | OPENSSL_cleanse(passphrase, passphrase_maxsize); 41 | return false; 42 | } 43 | 44 | return true; 45 | } 46 | 47 | void dump_hex_long(FILE *f, const void *vdata, unsigned int length) { 48 | const uint8_t *data = (const uint8_t*)vdata; 49 | for (unsigned int i = 0; i < length; i += 32) { 50 | fprintf(f, "%4x ", i); 51 | for (unsigned int j = i; j < i + 32; j++) { 52 | fprintf(f, "%02x", data[j]); 53 | } 54 | fprintf(f, "\n"); 55 | } 56 | } 57 | 58 | void sprintf_hex(char *dest, const uint8_t *data, unsigned int length) { 59 | for (unsigned int i = 0; i < length; i++) { 60 | sprintf(dest + (2 * i), "%02x", data[i]); 61 | } 62 | } 63 | 64 | void dump_hex(FILE *f, const void *vdata, unsigned int length, bool use_ascii) { 65 | const uint8_t *data = (const uint8_t*)vdata; 66 | for (unsigned int i = 0; i < length; i++) { 67 | uint8_t character = data[i]; 68 | if (use_ascii && (character > 32) && (character < 127)) { 69 | fprintf(f, "%c ", character); 70 | } else { 71 | fprintf(f, "%02x ", character); 72 | } 73 | } 74 | } 75 | 76 | void dump_hexline(FILE *f, const char *prefix, const void *vdata, unsigned int length, bool use_ascii) { 77 | if (prefix) { 78 | fprintf(f, "%s", prefix); 79 | } 80 | dump_hex(f, vdata, length, use_ascii); 81 | fprintf(f, "\n"); 82 | } 83 | 84 | bool is_hex(const char *str, int length) { 85 | for (int i = 0; i < length; i++) { 86 | if (((str[i] >= '0') && (str[i] <= '9')) || 87 | ((str[i] >= 'a') && (str[i] <= 'f')) || 88 | ((str[i] >= 'A') && (str[i] <= 'F'))) { 89 | continue; 90 | } 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | static int parse_nibble(char nibble) { 97 | if ((nibble >= '0') && (nibble <= '9')) { 98 | return nibble - '0'; 99 | } else if ((nibble >= 'a') && (nibble <= 'f')) { 100 | return nibble - 'a' + 10; 101 | } else if ((nibble >= 'A') && (nibble <= 'F')) { 102 | return nibble - 'A' + 10; 103 | } 104 | return -1; 105 | } 106 | 107 | static int parse_hexchar(const char *str) { 108 | int high = parse_nibble(str[0]); 109 | int low = parse_nibble(str[1]); 110 | if ((high == -1) || (low == -1)) { 111 | return -1; 112 | } 113 | return (high << 4) | low; 114 | } 115 | 116 | int parse_hexstr(const char *hexstr, uint8_t *data, int maxlen) { 117 | int length = 0; 118 | for (int i = 0; i < maxlen; i++) { 119 | if (*hexstr == 0) { 120 | break; 121 | } 122 | 123 | int next_char = parse_hexchar(hexstr); 124 | if (next_char == -1) { 125 | return -1; 126 | } 127 | data[length++] = next_char; 128 | hexstr += 2; 129 | } 130 | return length; 131 | } 132 | 133 | bool truncate_crlf(char *string) { 134 | int length = strlen(string); 135 | bool truncated = false; 136 | if (length && (string[length - 1] == '\n')) { 137 | truncated = true; 138 | string[--length] = 0; 139 | } 140 | if (length && (string[length - 1] == '\r')) { 141 | truncated = true; 142 | string[--length] = 0; 143 | } 144 | return truncated; 145 | } 146 | 147 | bool buffer_randomize(uint8_t *buffer, unsigned int length) { 148 | FILE *f = fopen("/dev/urandom", "r"); 149 | if (!f) { 150 | log_libc(LLVL_FATAL, "Failed to access /dev/urandom"); 151 | return false; 152 | } 153 | if (fread(buffer, length, 1, f) != 1) { 154 | log_libc(LLVL_FATAL, "Error reading randomness from /dev/urandom"); 155 | fclose(f); 156 | return false; 157 | } 158 | fclose(f); 159 | return true; 160 | } 161 | 162 | bool is_zero(const void *data, unsigned int length) { 163 | const uint8_t *bytedata = (const uint8_t*)data; 164 | for (unsigned int i = 0; i < length; i++) { 165 | if (bytedata[i]) { 166 | return false; 167 | } 168 | } 169 | return true; 170 | } 171 | 172 | bool array_remove(void *base, unsigned int element_size, unsigned int element_count, unsigned int remove_element_index) { 173 | if (remove_element_index >= element_count) { 174 | return false; 175 | } 176 | uint8_t *bytebase = (uint8_t*)base; 177 | const unsigned int destination_offset = remove_element_index * element_size; 178 | const unsigned int source_offset = (remove_element_index + 1) * element_size; 179 | const unsigned int copy_length = ((element_count - 1) - remove_element_index) * element_size; 180 | if (copy_length) { 181 | memcpy(bytebase + destination_offset, bytebase + source_offset, copy_length); 182 | } 183 | 184 | /* Then, wipe the last element */ 185 | const unsigned int last_element_offset = element_size * (element_count - 1); 186 | memset(bytebase + last_element_offset, 0, element_size); 187 | return true; 188 | } 189 | 190 | static uint8_t get_array_value(const uint8_t *array, unsigned int array_length, unsigned int array_index) { 191 | if (array_index < array_length) { 192 | return array[array_index]; 193 | } else { 194 | return 0; 195 | } 196 | } 197 | 198 | bool ascii_encode(char *dest, unsigned int dest_buffer_size, const uint8_t *source_data, unsigned int source_data_length) { 199 | const unsigned int require_dest_size = ((source_data_length + 2) / 3) * 4 + 1; 200 | if (dest_buffer_size < require_dest_size) { 201 | log_msg(LLVL_FATAL, "Encoding of %d bytes takes a %d byte buffer, but only %d bytes provided.", source_data_length, require_dest_size, dest_buffer_size); 202 | return false; 203 | } 204 | 205 | const char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 206 | for (unsigned int i = 0; i < source_data_length; i += 3) { 207 | uint32_t word = ((get_array_value(source_data, source_data_length, i + 0) << 16) | (get_array_value(source_data, source_data_length, i + 1) << 8) | (get_array_value(source_data, source_data_length, i + 2) << 0)); 208 | for (int shift = 18; shift >= 0; shift -= 6) { 209 | *dest++ = alphabet[(word >> shift) & 0x3f]; 210 | } 211 | } 212 | *dest = 0; 213 | return true; 214 | } 215 | 216 | double now(void) { 217 | struct timeval tv; 218 | if (gettimeofday(&tv, NULL)) { 219 | return 0; 220 | } 221 | return tv.tv_sec + (tv.tv_usec * 1e-6); 222 | } 223 | -------------------------------------------------------------------------------- /argparse_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was AUTO-GENERATED by pypgmopts. 3 | * 4 | * https://github.com/johndoe31415/pypgmopts 5 | * 6 | * Do not edit it by hand, your changes will be overwritten. 7 | * 8 | * Generated at: 2022-06-18 16:30:46 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "argparse_client.h" 20 | 21 | static enum argparse_client_option_t last_parsed_option; 22 | static char last_error_message[256]; 23 | static const char *option_texts[] = { 24 | [ARG_CLIENT_TIMEOUT] = "-t / --timeout", 25 | [ARG_CLIENT_PORT] = "-p / --port", 26 | [ARG_CLIENT_FORCE_UNLOCK_ALL] = "--force-unlock-all", 27 | [ARG_CLIENT_NO_LUKS] = "--no-luks", 28 | [ARG_CLIENT_VERBOSE] = "-v / --verbose", 29 | [ARG_CLIENT_FILENAME] = "filename", 30 | [ARG_CLIENT_HOSTNAME] = "hostname", 31 | }; 32 | 33 | enum argparse_client_option_internal_t { 34 | ARG_CLIENT_TIMEOUT_SHORT = 't', 35 | ARG_CLIENT_PORT_SHORT = 'p', 36 | ARG_CLIENT_VERBOSE_SHORT = 'v', 37 | ARG_CLIENT_TIMEOUT_LONG = 1000, 38 | ARG_CLIENT_PORT_LONG = 1001, 39 | ARG_CLIENT_FORCE_UNLOCK_ALL_LONG = 1002, 40 | ARG_CLIENT_NO_LUKS_LONG = 1003, 41 | ARG_CLIENT_VERBOSE_LONG = 1004, 42 | ARG_CLIENT_FILENAME_LONG = 1005, 43 | ARG_CLIENT_HOSTNAME_LONG = 1006, 44 | }; 45 | 46 | static void errmsg_callback(const char *errmsg, ...) { 47 | va_list ap; 48 | va_start(ap, errmsg); 49 | vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap); 50 | va_end(ap); 51 | } 52 | 53 | static void errmsg_option_callback(enum argparse_client_option_t error_option, const char *errmsg, ...) { 54 | last_parsed_option = error_option; 55 | 56 | va_list ap; 57 | va_start(ap, errmsg); 58 | vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap); 59 | va_end(ap); 60 | } 61 | 62 | bool argparse_client_parse(int argc, char **argv, argparse_client_callback_t argument_callback, argparse_client_plausibilization_callback_t plausibilization_callback) { 63 | last_parsed_option = ARGPARSE_CLIENT_NO_OPTION; 64 | const char *short_options = "t:p:v"; 65 | struct option long_options[] = { 66 | { "timeout", required_argument, 0, ARG_CLIENT_TIMEOUT_LONG }, 67 | { "port", required_argument, 0, ARG_CLIENT_PORT_LONG }, 68 | { "force-unlock-all", no_argument, 0, ARG_CLIENT_FORCE_UNLOCK_ALL_LONG }, 69 | { "no-luks", no_argument, 0, ARG_CLIENT_NO_LUKS_LONG }, 70 | { "verbose", no_argument, 0, ARG_CLIENT_VERBOSE_LONG }, 71 | { "filename", required_argument, 0, ARG_CLIENT_FILENAME_LONG }, 72 | { "hostname", required_argument, 0, ARG_CLIENT_HOSTNAME_LONG }, 73 | { 0 } 74 | }; 75 | 76 | while (true) { 77 | int optval = getopt_long(argc, argv, short_options, long_options, NULL); 78 | if (optval == -1) { 79 | break; 80 | } 81 | last_error_message[0] = 0; 82 | enum argparse_client_option_internal_t arg = (enum argparse_client_option_internal_t)optval; 83 | switch (arg) { 84 | case ARG_CLIENT_TIMEOUT_SHORT: 85 | case ARG_CLIENT_TIMEOUT_LONG: 86 | last_parsed_option = ARG_CLIENT_TIMEOUT; 87 | if (!argument_callback(ARG_CLIENT_TIMEOUT, optarg, errmsg_callback)) { 88 | return false; 89 | } 90 | break; 91 | 92 | case ARG_CLIENT_PORT_SHORT: 93 | case ARG_CLIENT_PORT_LONG: 94 | last_parsed_option = ARG_CLIENT_PORT; 95 | if (!argument_callback(ARG_CLIENT_PORT, optarg, errmsg_callback)) { 96 | return false; 97 | } 98 | break; 99 | 100 | case ARG_CLIENT_FORCE_UNLOCK_ALL_LONG: 101 | last_parsed_option = ARG_CLIENT_FORCE_UNLOCK_ALL; 102 | if (!argument_callback(ARG_CLIENT_FORCE_UNLOCK_ALL, optarg, errmsg_callback)) { 103 | return false; 104 | } 105 | break; 106 | 107 | case ARG_CLIENT_NO_LUKS_LONG: 108 | last_parsed_option = ARG_CLIENT_NO_LUKS; 109 | if (!argument_callback(ARG_CLIENT_NO_LUKS, optarg, errmsg_callback)) { 110 | return false; 111 | } 112 | break; 113 | 114 | case ARG_CLIENT_VERBOSE_SHORT: 115 | case ARG_CLIENT_VERBOSE_LONG: 116 | last_parsed_option = ARG_CLIENT_VERBOSE; 117 | if (!argument_callback(ARG_CLIENT_VERBOSE, optarg, errmsg_callback)) { 118 | return false; 119 | } 120 | break; 121 | 122 | default: 123 | last_parsed_option = ARGPARSE_CLIENT_NO_OPTION; 124 | errmsg_callback("unrecognized option supplied"); 125 | return false; 126 | } 127 | } 128 | 129 | const int positional_argument_cnt = argc - optind; 130 | const int flexible_positional_args_cnt = positional_argument_cnt - 1; 131 | last_parsed_option = ARGPARSE_CLIENT_POSITIONAL_ARG; 132 | if (positional_argument_cnt < 1) { 133 | errmsg_callback("expected a minimum of 1 positional argument, but %d given.", positional_argument_cnt); 134 | return false; 135 | } 136 | if (positional_argument_cnt > 2) { 137 | errmsg_callback("expected a maximum of 2 positional arguments, but %d given.", positional_argument_cnt); 138 | return false; 139 | } 140 | 141 | int positional_index = optind; 142 | last_parsed_option = ARG_CLIENT_FILENAME; 143 | if (!argument_callback(ARG_CLIENT_FILENAME, argv[positional_index++], errmsg_callback)) { 144 | return false; 145 | } 146 | last_parsed_option = ARG_CLIENT_HOSTNAME; 147 | for (int i = 0; i < flexible_positional_args_cnt; i++) { 148 | if (!argument_callback(ARG_CLIENT_HOSTNAME, argv[positional_index++], errmsg_callback)) { 149 | return false; 150 | } 151 | } 152 | 153 | if (plausibilization_callback) { 154 | if (!plausibilization_callback(errmsg_option_callback)) { 155 | return false; 156 | } 157 | } 158 | return true; 159 | } 160 | 161 | void argparse_client_show_syntax(void) { 162 | fprintf(stderr, "usage: luksrku client [-t secs] [-p port] [--force-unlock-all] [--no-luks] [-v] filename [hostname]\n"); 163 | fprintf(stderr, "\n"); 164 | fprintf(stderr, "Connects to a luksrku key server and unlocks local LUKS volumes.\n"); 165 | fprintf(stderr, "\n"); 166 | fprintf(stderr, "positional arguments:\n"); 167 | fprintf(stderr, " filename Exported database file to load TLS-PSKs and list of disks from.\n"); 168 | fprintf(stderr, " hostname When hostname is given, auto-searching for suitable servers is disabled and\n"); 169 | fprintf(stderr, " only a connection to the given hostname is attempted.\n"); 170 | fprintf(stderr, "\n"); 171 | fprintf(stderr, "options:\n"); 172 | fprintf(stderr, " -t secs, --timeout secs\n"); 173 | fprintf(stderr, " When searching for a keyserver and not all volumes can be unlocked, abort\n"); 174 | fprintf(stderr, " after this period of time, given in seconds. Defaults to infinity. This\n"); 175 | fprintf(stderr, " argument can be specified as a host-based configuration parameter as well;\n"); 176 | fprintf(stderr, " the command-line argument always takes precedence.\n"); 177 | fprintf(stderr, " -p port, --port port Port that is used for both UDP and TCP communication. Defaults to 23170.\n"); 178 | fprintf(stderr, " --force-unlock-all Force attempting of unlock of all volumes, regardless if they're already\n"); 179 | fprintf(stderr, " unlocked or not. Useful for testing unlocking procedure.\n"); 180 | fprintf(stderr, " --no-luks Do not call LUKS/cryptsetup. Useful for testing unlocking procedure.\n"); 181 | fprintf(stderr, " -v, --verbose Increase verbosity. Can be specified multiple times.\n"); 182 | } 183 | 184 | void argparse_client_parse_or_quit(int argc, char **argv, argparse_client_callback_t argument_callback, argparse_client_plausibilization_callback_t plausibilization_callback) { 185 | if (!argparse_client_parse(argc, argv, argument_callback, plausibilization_callback)) { 186 | if (last_parsed_option > ARGPARSE_CLIENT_POSITIONAL_ARG) { 187 | if (last_error_message[0]) { 188 | fprintf(stderr, "luksrku client: error parsing argument %s -- %s\n", option_texts[last_parsed_option], last_error_message); 189 | } else { 190 | fprintf(stderr, "luksrku client: error parsing argument %s -- no details available\n", option_texts[last_parsed_option]); 191 | } 192 | } else if (last_parsed_option == ARGPARSE_CLIENT_POSITIONAL_ARG) { 193 | fprintf(stderr, "luksrku client: error parsing optional arguments -- %s\n", last_error_message); 194 | } 195 | argparse_client_show_syntax(); 196 | exit(EXIT_FAILURE); 197 | } 198 | } 199 | 200 | #ifdef __ARGPARSE_MAIN__ 201 | /* gcc -D __ARGPARSE_MAIN__ -O2 -Wall -o argparse argparse_client.c 202 | */ 203 | 204 | static const char *option_enum_to_str(enum argparse_client_option_t option) { 205 | switch (option) { 206 | case ARG_CLIENT_TIMEOUT: return "ARG_CLIENT_TIMEOUT"; 207 | case ARG_CLIENT_PORT: return "ARG_CLIENT_PORT"; 208 | case ARG_CLIENT_FORCE_UNLOCK_ALL: return "ARG_CLIENT_FORCE_UNLOCK_ALL"; 209 | case ARG_CLIENT_NO_LUKS: return "ARG_CLIENT_NO_LUKS"; 210 | case ARG_CLIENT_VERBOSE: return "ARG_CLIENT_VERBOSE"; 211 | case ARG_CLIENT_FILENAME: return "ARG_CLIENT_FILENAME"; 212 | case ARG_CLIENT_HOSTNAME: return "ARG_CLIENT_HOSTNAME"; 213 | } 214 | return "UNKNOWN"; 215 | } 216 | 217 | bool arg_print_callback(enum argparse_client_option_t option, const char *value, argparse_client_errmsg_callback_t errmsg_callback) { 218 | fprintf(stderr, "%s = \"%s\"\n", option_enum_to_str(option), value); 219 | return true; 220 | } 221 | 222 | int main(int argc, char **argv) { 223 | argparse_client_parse_or_quit(argc, argv, arg_print_callback, NULL); 224 | return 0; 225 | } 226 | #endif 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # luksrku 2 | luksrku is a tool that allows you to remotely unlock LUKS disks during boot up 3 | from within your initrd. The intention is to have full-disk-encryption with 4 | LUKS-rootfs running headlessly. You should be able to remotely unlock their 5 | LUKS cryptographic file systems when you know they have been (legitimately) 6 | rebooted. 7 | 8 | This works as follows: The luksrku client (which needs unlocking) and luksrku 9 | server (which holds all the LUKS keys) share a secret. The client either knows 10 | the address of the server or it can issue a broadcast in the network to find 11 | the correct one. With the help of the shared secret, a TLS connection is 12 | established between the client and a legitimate server (who also knows the same 13 | secret). The server then tells the client all the LUKS passphrases, which 14 | performs luksOpen on all volumes. 15 | 16 | ## Security 17 | luksrku uses TLSv1.3-PSK with forward-secrecy key shares (i.e., ECDHE). The 18 | curves that are used for key agreement are X448 and X25519. 19 | TLS_CHACHA20_POLY1305_SHA256 or TLS_AES_256_GCM_SHA384 are accepted as cipher 20 | suites. 21 | 22 | The TLS PSKs are 256 bit long and randomly generated (`/dev/urandom`). 23 | Likewise, the LUKS passphrases are based on 256 bit long secrets, also 24 | generated from `/dev/urandom`, and are converted to Base64 for easier handling 25 | (when setting up everything initially). 26 | 27 | The binary protocol that runs between server and client is intentionally 28 | extremely simple to allow for easy code review. It exclusively uses fixed 29 | message lengths. There are two portions to it, an UDP and a TCP portion: 30 | 31 | Via UDP, a client broadcasts its client UUID (randomly generated when creating 32 | the client in the database) on the network (port 23170). A server then can 33 | check if it's key database contains that client's LUKS keys. If it does, the 34 | server will respond with a fixed unicast UDP datagram. The client receives this 35 | datagram and tries to establish a TCP connection to that server on the luksrku 36 | port 23170. This connection is secured using TLSv1.3-PSK, i.e., even when the 37 | UDP messages are spoofed/forged, a successful connection will only then happen 38 | if the server and client share the same, previously defined, PSK. 39 | 40 | For persistent storage, the key database is encrypted, using AES256-GCM. A 128 41 | bit randomized initialization vector is used and all data is authenticated with 42 | a 128 bit authentication tag. Key derivation is done using scrypt with N = 43 | 262144 = 2^18, r = 8, p = 1 (although this is flexible in code and can be 44 | easily adapted). 45 | 46 | When the key database is not in use, the server encrypts all LUKS passphrases 47 | and PSKs in-memory (again, using AES256-GCM). A large, 1 MiB pre-key is also 48 | kept in memory. The AES key is derived from this pre-key using 49 | PBKDF2-HMAC-SHA256 and an iteration count that results in ~25ms key derivation. 50 | While it might seem nonsensical to encrypt memory and have the key right next 51 | to the encrypted data, the reason for this this is to thwart cold-boot attacks. 52 | A successful cold-boot attack would require a complete and perfect 1 MiB 53 | snapshot of the pre-key (or an acquisition in the short timeframe where the 54 | key vault is open) -- something that is difficult to do because of naturally 55 | occurring bit errors during cold boot acquisition. 56 | 57 | ## Dependencies 58 | OpenSSL v1.1 is required for luksrku as well as pkg-config. 59 | 60 | ## Usage 61 | The help pages of luksrku are fairly well documented, i.e.: 62 | 63 | ``` 64 | $ ./luksrku 65 | error: no command supplied 66 | 67 | Available commands: 68 | ./luksrku edit Interactively edit a key database 69 | ./luksrku server Start a key server process 70 | ./luksrku client Unlock LUKS volumes by querying a key server 71 | 72 | For further help: ./luksrku (command) --help 73 | 74 | luksrku version v0.02-45-gf01ec97d6b-dirty 75 | ``` 76 | 77 | Then, for each command, you have an own help page: 78 | 79 | ``` 80 | $ ./luksrku edit --help 81 | usage: luksrku edit [-v] [filename] 82 | 83 | Edits a luksrku key database. 84 | 85 | positional arguments: 86 | filename Database file to edit. 87 | 88 | optional arguments: 89 | -v, --verbose Increase verbosity. Can be specified multiple times. 90 | ``` 91 | 92 | ``` 93 | $ ./luksrku server --help 94 | usage: luksrku server [-p port] [-s] [-v] filename 95 | 96 | Starts a luksrku key server. 97 | 98 | positional arguments: 99 | filename Database file to load keys from. 100 | 101 | optional arguments: 102 | -p port, --port port Port that is used for both UDP and TCP communication. 103 | Defaults to 23170. 104 | -s, --silent Do not answer UDP queries for clients trying to find a 105 | key server, only serve key database using TCP. 106 | -v, --verbose Increase verbosity. Can be specified multiple times. 107 | ``` 108 | 109 | ``` 110 | $ ./luksrku client --help 111 | usage: luksrku client [-t secs] [-p port] [--no-luks] [-v] filename [hostname] 112 | 113 | Connects to a luksrku key server and unlocks local LUKS volumes. 114 | 115 | positional arguments: 116 | filename Exported database file to load TLS-PSKs and list of 117 | disks from. 118 | hostname When hostname is given, auto-searching for suitable 119 | servers is disabled and only a connection to the given 120 | hostname is attempted. 121 | 122 | optional arguments: 123 | -t secs, --timeout secs 124 | When searching for a keyserver and not all volumes can 125 | be unlocked, abort after this period of time, given in 126 | seconds. Defaults to 60 seconds. 127 | -p port, --port port Port that is used for both UDP and TCP communication. 128 | Defaults to 23170. 129 | --no-luks Do not call LUKS/cryptsetup. Useful for testing 130 | unlocking procedure. 131 | -v, --verbose Increase verbosity. Can be specified multiple times. 132 | ``` 133 | 134 | ## Example 135 | First, you need to create a server key database. For this you use the editor: 136 | 137 | ``` 138 | $ ./luksrku edit 139 | > add_host my_host 140 | ``` 141 | 142 | Now there's a host "my_host" in the key database. At any point you can inspect 143 | the database by using the "list" command: 144 | 145 | ``` 146 | Keydb version 2, server database, 1 hosts. 147 | Host 1: "my_host" UUID e7ff6e3d-1793-48f6-b43b-9c7bb0348622 -- 0 volumes: 148 | ``` 149 | 150 | You'll see that the host has no volumes associated with it. Determine the UUID 151 | of the LUKS device that you want luksrku to decrypt, then add this volume with 152 | the name you want it to have after unlocking. In our case, the UUID is 153 | 18de9f14-2914-4a8b-9b46-b7deacbfbe8a and we want it to decrypt as "crypt-root": 154 | 155 | ``` 156 | > add_volume my_host crypt-root 18de9f14-2914-4a8b-9b46-b7deacbfbe8a 157 | LUKS passphrase of crypt-root / 18de9f14-2914-4a8b-9b46-b7deacbfbe8a: 5DySDFcpVtBRoIMNv7mrLqlozPYeq7X5kPmB3M1wsW8A 158 | ``` 159 | 160 | At this point, luksrku will tell you, in clear text, the LUKS passphrase that 161 | you need to add to the volume. Then, you save the server database: 162 | 163 | ``` 164 | > save server.bin 165 | Database passphrase: 166 | ``` 167 | 168 | It asks you for a passphrase that is needed to decrypt the file. On disk it's 169 | always stored encrypted. Using an encrypted server database is highly 170 | recommended. 171 | 172 | For the client, you export the client portion of the database: 173 | 174 | ``` 175 | > export my_host my_host.bin 176 | Client passphrase: 177 | ``` 178 | 179 | Note that client databases can also be encrypted, but they're less critical 180 | than the server database. The client database does *not* contain the LUKS 181 | passphrases, it only contains the required TLS-PSK so that a successful 182 | connection to a luksrku server can be established. 183 | 184 | With these two in place, you can now start a luksrku server: 185 | 186 | ``` 187 | $ ./luksrku server server.bin 188 | Database passphrase: 189 | [I]: Serving luksrku database for 1 hosts. 190 | ``` 191 | 192 | And on your client, when you want the LUKS disks to be unlocked: 193 | 194 | ``` 195 | $ ./luksrku client my_host.bin 196 | ``` 197 | 198 | ## Integration into initramfs 199 | Using luksrku as part of your initramfs is quite easy. You'll need a server 200 | somewhere in your network and an exported client database. On the client, you 201 | copy the client database file into `/etc/luksrku-client.bin`. 202 | 203 | Then, install luksrku globally by performing `make install` as root and install 204 | the initramfs script by running `install` in the initramfs/ subdirectory. 205 | You'll only need to install that once. 206 | 207 | ``` 208 | # make install 209 | strip luksrku 210 | cp luksrku /usr/local/sbin/ 211 | chown root:root /usr/local/sbin/luksrku 212 | chmod 755 /usr/local/sbin/luksrku 213 | # cd initramfs 214 | # ./install 215 | ``` 216 | 217 | Finally, have initramfs recreate your initial ramdisk: 218 | 219 | ``` 220 | # update-initramfs -u 221 | ``` 222 | 223 | During boot, you have several kernel command line options to control the 224 | behavior of luksrku by appending a `luksrku=opt1,opt2,opt3,...` line. Valid 225 | options are: 226 | 227 | - `off`: Disable luksrku unlocking entirely (including network configuration) 228 | - `timeout=n`: Give up after `n` seconds. By default, looks infinitely long. 229 | - `host=xyz`: Ask only host `xyz` for credentials. By default, looks for a 230 | luksrku server using UDP broadcast. 231 | - `verbose=n`: Set verbosity level. Can be 1 or 2. By default, luksrku is 232 | silent. 233 | 234 | Example for valid kernel commandlines are: 235 | 236 | - `luksrku=timeout=30,host=192.168.1.9,verbose=2` 237 | - `luksrku=off` 238 | - `luksrku=verbose=1` 239 | 240 | ## License 241 | GNU GPL-3. 242 | -------------------------------------------------------------------------------- /vault.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "vault.h" 33 | #include "util.h" 34 | #include "log.h" 35 | 36 | static bool vault_derive_key(const struct vault_t *vault, uint8_t dkey[static 32]) { 37 | /* Derive the AES key from it */ 38 | if (PKCS5_PBKDF2_HMAC((char*)vault->source_key, vault->source_key_length, NULL, 0, vault->iteration_cnt, EVP_sha256(), 32, dkey) != 1) { 39 | return false; 40 | } 41 | return true; 42 | } 43 | 44 | static bool vault_rekey(struct vault_t *vault) { 45 | /* Generate a new source key */ 46 | if (RAND_bytes(vault->source_key, vault->source_key_length) != 1) { 47 | return false; 48 | } 49 | return vault_derive_key(vault, vault->dkey); 50 | } 51 | 52 | static double vault_measure_key_derivation_time(struct vault_t *vault, unsigned int new_iteration_count) { 53 | uint8_t dkey[32]; 54 | double t0, t1; 55 | vault->iteration_cnt = new_iteration_count; 56 | t0 = now(); 57 | vault_derive_key(vault, dkey); 58 | t1 = now(); 59 | OPENSSL_cleanse(dkey, sizeof(dkey)); 60 | return t1 - t0; 61 | } 62 | 63 | static void vault_calibrate_derivation_time(struct vault_t *vault, double target_derivation_time) { 64 | unsigned int iteration_cnt = 1; 65 | while (iteration_cnt < 100000000) { 66 | double current_time = vault_measure_key_derivation_time(vault, iteration_cnt); 67 | // fprintf(stderr, "%d: %f %f\n", iteration_cnt, current_time, target_derivation_time); 68 | if (current_time * 10 < target_derivation_time) { 69 | iteration_cnt *= 2; 70 | } else if (current_time * 1.1 < target_derivation_time) { 71 | unsigned int new_iteration_cnt = iteration_cnt * target_derivation_time / current_time; 72 | if (new_iteration_cnt == iteration_cnt) { 73 | break; 74 | } 75 | iteration_cnt = new_iteration_cnt; 76 | } else { 77 | break; 78 | } 79 | } 80 | } 81 | 82 | struct vault_t* vault_init(unsigned int data_length, double target_decryption_time) { 83 | struct vault_t *vault; 84 | 85 | vault = calloc(1, sizeof(struct vault_t)); 86 | if (!vault) { 87 | return NULL; 88 | } 89 | 90 | if (pthread_mutex_init(&vault->mutex, NULL)) { 91 | log_libc(LLVL_FATAL, "Unable to initialize vault mutex."); 92 | free(vault); 93 | return NULL; 94 | } 95 | vault->source_key_length = DEFAULT_SOURCE_KEY_LENGTH_BYTES; 96 | vault->source_key = malloc(vault->source_key_length); 97 | if (!vault->source_key) { 98 | vault_free(vault); 99 | return NULL; 100 | } 101 | 102 | vault->data = calloc(data_length, 1); 103 | if (!vault->data) { 104 | vault_free(vault); 105 | return NULL; 106 | } 107 | vault->reference_count = 1; 108 | vault->data_length = data_length; 109 | 110 | /* Decryption takes *two* derivations, one for the current key (to decrypt) 111 | * and another in advance after re-keying, therefore we halve the time 112 | * here. */ 113 | vault_calibrate_derivation_time(vault, target_decryption_time / 2); 114 | 115 | /* Initially gernerate a full key and derive the dkey already (vault is 116 | * open at this point) */ 117 | if (!vault_rekey(vault)) { 118 | vault_free(vault); 119 | return NULL; 120 | } 121 | 122 | return vault; 123 | } 124 | 125 | static void vault_destroy_content(struct vault_t *vault) { 126 | if (vault->data) { 127 | OPENSSL_cleanse(vault->data, vault->data_length); 128 | } 129 | if (vault->source_key) { 130 | OPENSSL_cleanse(vault->source_key, vault->source_key_length); 131 | } 132 | } 133 | 134 | static bool vault_decrypt(struct vault_t *vault) { 135 | /* At this point we only have the source key, not the dkey yet. Derive the 136 | * dkey into a local piece of memory first */ 137 | uint8_t dkey[32]; 138 | if (!vault_derive_key(vault, dkey)) { 139 | OPENSSL_cleanse(dkey, sizeof(dkey)); 140 | return false; 141 | } 142 | 143 | /* Then rekey the vault for the upcoming closing. Do this while the vault 144 | * is still encrypted to minimize window of opportunity. */ 145 | if (!vault_rekey(vault)) { 146 | OPENSSL_cleanse(dkey, sizeof(dkey)); 147 | return false; 148 | } 149 | 150 | EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); 151 | if (!ctx) { 152 | return false; 153 | } 154 | 155 | bool success = true; 156 | do { 157 | if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) != 1) { 158 | success = false; 159 | break; 160 | } 161 | 162 | if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, sizeof(uint64_t), NULL) != 1) { 163 | success = false; 164 | break; 165 | } 166 | 167 | if (EVP_DecryptInit_ex(ctx, NULL, NULL, dkey, (unsigned char*)&vault->iv) != 1) { 168 | success = false; 169 | break; 170 | } 171 | 172 | int len = 0; 173 | if (EVP_DecryptUpdate(ctx, vault->data, &len, vault->data, vault->data_length) != 1) { 174 | success = false; 175 | break; 176 | } 177 | 178 | if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, vault->auth_tag) != 1) { 179 | success = false; 180 | break; 181 | } 182 | 183 | if (EVP_DecryptFinal_ex(ctx, (uint8_t*)vault->data + len, &len) != 1) { 184 | success = false; 185 | break; 186 | } 187 | } while (false); 188 | 189 | if (!success) { 190 | /* Vault may be in an inconsistent state. Destroy contents. */ 191 | vault_destroy_content(vault); 192 | } 193 | 194 | OPENSSL_cleanse(dkey, sizeof(dkey)); 195 | OPENSSL_cleanse(vault->auth_tag, 16); 196 | EVP_CIPHER_CTX_free(ctx); 197 | return success; 198 | } 199 | 200 | bool vault_open(struct vault_t *vault) { 201 | bool success = true; 202 | pthread_mutex_lock(&vault->mutex); 203 | vault->reference_count++; 204 | if (vault->reference_count == 1) { 205 | /* Vault was closed, we need to decrypt it. */ 206 | success = vault_decrypt(vault); 207 | } 208 | pthread_mutex_unlock(&vault->mutex); 209 | return success; 210 | } 211 | 212 | static bool vault_encrypt(struct vault_t *vault) { 213 | /* We already have a dkey in the structure, so we can quickly encrypt */ 214 | EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); 215 | if (!ctx) { 216 | return false; 217 | } 218 | 219 | /* IV doesn't really make sense here because we never reuse the key, but we 220 | * still do it for good measure (in case someone copies & pastes our code 221 | * into a different application). */ 222 | bool success = true; 223 | do { 224 | vault->iv++; 225 | if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) != 1) { 226 | success = false; 227 | break; 228 | } 229 | 230 | if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, sizeof(uint64_t), NULL) != 1) { 231 | success = false; 232 | break; 233 | } 234 | 235 | if (EVP_EncryptInit_ex(ctx, NULL, NULL, vault->dkey, (unsigned char*)&vault->iv) != 1) { 236 | success = false; 237 | break; 238 | } 239 | 240 | int len = 0; 241 | if (EVP_EncryptUpdate(ctx, vault->data, &len, vault->data, vault->data_length) != 1) { 242 | success = false; 243 | break; 244 | } 245 | 246 | if (EVP_EncryptFinal_ex(ctx, (uint8_t*)vault->data + len, &len) != 1) { 247 | success = false; 248 | break; 249 | } 250 | 251 | if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, vault->auth_tag) != 1) { 252 | success = false; 253 | break; 254 | } 255 | } while (false); 256 | 257 | /* The data is encrypted, erase the dkey, but keep the source key (so we 258 | * can decrypt later) */ 259 | OPENSSL_cleanse(vault->dkey, sizeof(vault->dkey)); 260 | 261 | if (!success) { 262 | /* Vault may be in an inconsistent state. Destroy contents. */ 263 | vault_destroy_content(vault); 264 | } 265 | 266 | EVP_CIPHER_CTX_free(ctx); 267 | return success; 268 | } 269 | 270 | bool vault_close(struct vault_t *vault) { 271 | bool success = true; 272 | pthread_mutex_lock(&vault->mutex); 273 | vault->reference_count--; 274 | if (vault->reference_count == 0) { 275 | /* Vault is now closed, we need to encrypt it. */ 276 | success = vault_encrypt(vault); 277 | } 278 | pthread_mutex_unlock(&vault->mutex); 279 | return success; 280 | } 281 | 282 | 283 | void vault_free(struct vault_t *vault) { 284 | if (!vault) { 285 | return; 286 | } 287 | pthread_mutex_destroy(&vault->mutex); 288 | vault_destroy_content(vault); 289 | free(vault->data); 290 | free(vault->source_key); 291 | free(vault); 292 | } 293 | 294 | #ifdef __TEST_VAULT__ 295 | 296 | static void dump(const uint8_t *data, unsigned int length) { 297 | for (unsigned int i = 0; i < length; i++) { 298 | fprintf(stderr, "%02x ", data[i]); 299 | } 300 | fprintf(stderr, "\n"); 301 | } 302 | 303 | int main(void) { 304 | /* gcc -D__TEST_VAULT__ -Wall -std=c11 -Wmissing-prototypes -Wstrict-prototypes -Werror=implicit-function-declaration -Wimplicit-fallthrough -Wshadow -pie -fPIE -fsanitize=address -fsanitize=undefined -fsanitize=leak -pthread -o vault vault.c util.c log.c -lcrypto 305 | */ 306 | struct vault_t *vault = vault_init(64, 1); 307 | dump(vault->data, vault->data_length); 308 | for (int i = 0; i < 10; i++) { 309 | if (!vault_close(vault)) { 310 | fprintf(stderr, "vault close failed.\n"); 311 | abort(); 312 | } 313 | dump(vault->data, vault->data_length); 314 | 315 | if (!vault_open(vault)) { 316 | fprintf(stderr, "vault open failed.\n"); 317 | abort(); 318 | } 319 | dump(vault->data, vault->data_length); 320 | } 321 | vault_free(vault); 322 | return 0; 323 | } 324 | #endif 325 | -------------------------------------------------------------------------------- /server.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | 38 | #include "log.h" 39 | #include "openssl.h" 40 | #include "global.h" 41 | #include "msg.h" 42 | #include "util.h" 43 | #include "server.h" 44 | #include "luks.h" 45 | #include "pgmopts.h" 46 | #include "uuid.h" 47 | #include "thread.h" 48 | #include "keydb.h" 49 | #include "signals.h" 50 | #include "udp.h" 51 | #include "blacklist.h" 52 | #include "vaulted_keydb.h" 53 | 54 | struct keyserver_t { 55 | keydb_t* keydb; 56 | struct vaulted_keydb_t *vaulted_keydb; 57 | struct generic_tls_ctx_t gctx; 58 | const struct pgmopts_server_t *opts; 59 | int tcp_sd, udp_sd; 60 | }; 61 | 62 | struct client_thread_ctx_t { 63 | struct generic_tls_ctx_t *gctx; 64 | const keydb_t *keydb; 65 | struct vaulted_keydb_t *vaulted_keydb; 66 | const host_entry_t *host; 67 | int fd; 68 | }; 69 | 70 | struct udp_listen_thread_ctx_t { 71 | const keydb_t *keydb; 72 | int udp_sd; 73 | unsigned int port; 74 | }; 75 | 76 | static int create_tcp_server_socket(int port) { 77 | int sd = socket(AF_INET, SOCK_STREAM, 0); 78 | if (sd < 0) { 79 | log_libc(LLVL_ERROR, "Unable to create TCP socket(2)"); 80 | return -1; 81 | } 82 | 83 | { 84 | int value = 1; 85 | setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)); 86 | } 87 | 88 | struct sockaddr_in addr = { 89 | .sin_family = AF_INET, 90 | .sin_port = htons(port), 91 | .sin_addr.s_addr = htonl(INADDR_ANY), 92 | }; 93 | if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { 94 | log_libc(LLVL_ERROR, "Unable to bind(2) socket"); 95 | return -1; 96 | } 97 | 98 | if (listen(sd, 1) < 0) { 99 | log_libc(LLVL_ERROR, "Unable to listen(2) on socket"); 100 | return -1; 101 | } 102 | 103 | return sd; 104 | } 105 | 106 | static int psk_server_callback(SSL *ssl, const unsigned char *identity, size_t identity_len, SSL_SESSION **sessptr) { 107 | struct client_thread_ctx_t *ctx = (struct client_thread_ctx_t*)SSL_get_app_data(ssl); 108 | 109 | if (identity_len != ASCII_UUID_CHARACTER_COUNT) { 110 | log_msg(LLVL_WARNING, "Received client identity of length %ld, cannot be a UUID.", identity_len); 111 | return 0; 112 | } 113 | 114 | char uuid_str[ASCII_UUID_BUFSIZE]; 115 | memcpy(uuid_str, identity, ASCII_UUID_CHARACTER_COUNT); 116 | uuid_str[ASCII_UUID_CHARACTER_COUNT] = 0; 117 | if (!is_valid_uuid(uuid_str)) { 118 | log_msg(LLVL_WARNING, "Received client identity of length %ld, but not a valid UUID.", identity_len); 119 | return 0; 120 | } 121 | 122 | uint8_t uuid[16]; 123 | if (!parse_uuid(uuid, uuid_str)) { 124 | log_msg(LLVL_ERROR, "Failed to parse valid UUID."); 125 | return 0; 126 | } 127 | 128 | ctx->host = keydb_get_host_by_uuid(ctx->keydb, uuid); 129 | if (!ctx->host) { 130 | log_msg(LLVL_WARNING, "Client connected with client UUID %s, but not present in key database.", uuid_str); 131 | return 0; 132 | } 133 | 134 | uint8_t psk[PSK_SIZE_BYTES]; 135 | if (!vaulted_keydb_get_tls_psk(ctx->vaulted_keydb, psk, ctx->host)) { 136 | log_msg(LLVL_WARNING, "Cannot establish server connection without TLS-PSK."); 137 | return 0; 138 | } 139 | 140 | int result = openssl_tls13_psk_establish_session(ssl, psk, PSK_SIZE_BYTES, EVP_sha256(), sessptr); 141 | OPENSSL_cleanse(psk, PSK_SIZE_BYTES); 142 | return result; 143 | } 144 | 145 | static void copy_luks_passphrase_callback(void *vctx, unsigned int volume_index, const void *source) { 146 | struct msg_t *msgs = (struct msg_t*)vctx; 147 | memcpy(msgs[volume_index].luks_passphrase_raw, source, LUKS_PASSPHRASE_RAW_SIZE_BYTES); 148 | } 149 | 150 | static void client_handler_thread(void *vctx) { 151 | struct client_thread_ctx_t *client = (struct client_thread_ctx_t*)vctx; 152 | 153 | SSL *ssl = SSL_new(client->gctx->ctx); 154 | if (ssl) { 155 | SSL_set_fd(ssl, client->fd); 156 | SSL_set_app_data(ssl, client); 157 | 158 | if (SSL_accept(ssl) <= 0) { 159 | log_openssl(LLVL_WARNING, "Could not establish TLS connection to connecting client."); 160 | ERR_print_errors_fp(stderr); 161 | } else { 162 | if (client->host) { 163 | log_msg(LLVL_DEBUG, "Client \"%s\" connected, sending unlock data for %d volumes.", client->host->host_name, client->host->volume_count); 164 | /* Initially prepare all messages we're about to send to the 165 | * client by filling the UUID fields */ 166 | struct msg_t msgs[client->host->volume_count]; 167 | for (unsigned int i = 0; i < client->host->volume_count; i++) { 168 | const volume_entry_t *volume = &client->host->volumes[i]; 169 | memcpy(msgs[i].volume_uuid, volume->volume_uuid, 16); 170 | } 171 | 172 | /* Then also fill the keys */ 173 | vaulted_keydb_get_volume_luks_passphases_raw(client->vaulted_keydb, copy_luks_passphrase_callback, msgs, client->host); 174 | 175 | int txlen = SSL_write(ssl, &msgs, sizeof(msgs)); 176 | OPENSSL_cleanse(&msgs, sizeof(msgs)); 177 | if (txlen != (long)sizeof(msgs)) { 178 | log_msg(LLVL_WARNING, "Tried to send message of %ld bytes, but sent %d. Severing connection to client.", sizeof(msgs), txlen); 179 | } 180 | } else { 181 | log_msg(LLVL_FATAL, "Client connected, but no host set."); 182 | } 183 | } 184 | } else { 185 | log_openssl(LLVL_FATAL, "Cannot establish SSL context for connecting client"); 186 | } 187 | SSL_free(ssl); 188 | shutdown(client->fd, SHUT_RDWR); 189 | close(client->fd); 190 | } 191 | 192 | static void udp_handler_thread(void *vctx) { 193 | struct udp_listen_thread_ctx_t *client = (struct udp_listen_thread_ctx_t*)vctx; 194 | 195 | while (true) { 196 | struct udp_query_t rx_msg; 197 | struct sockaddr_in origin; 198 | if (!wait_udp_query(client->udp_sd, &rx_msg, &origin)) { 199 | continue; 200 | } 201 | 202 | log_msg(LLVL_TRACE, "Recevied UDP query message from %d.%d.%d.%d:%d", PRINTF_FORMAT_IP(&origin), ntohs(origin.sin_port)); 203 | 204 | /* Ensure that we only reply to this host once every minute */ 205 | const uint32_t ipv4 = origin.sin_addr.s_addr; 206 | if (is_ip_blacklisted(ipv4)) { 207 | continue; 208 | } 209 | blacklist_ip(ipv4, BLACKLIST_TIMEOUT_SERVER); 210 | 211 | /* Check if we have this host in our database */ 212 | if (keydb_get_host_by_uuid(client->keydb, rx_msg.host_uuid)) { 213 | /* Yes, it is. Notify the client who's asking that we have their key. */ 214 | struct udp_response_t tx_msg; 215 | memcpy(tx_msg.magic, UDP_MESSAGE_MAGIC, UDP_MESSAGE_MAGIC_SIZE); 216 | send_udp_message(client->udp_sd, &origin, &tx_msg, sizeof(tx_msg), true); 217 | } 218 | } 219 | } 220 | 221 | bool keyserver_start(const struct pgmopts_server_t *opts) { 222 | bool success = true; 223 | struct keyserver_t keyserver = { 224 | .opts = opts, 225 | .tcp_sd = -1, 226 | .udp_sd = -1, 227 | }; 228 | do { 229 | /* We ignore SIGPIPE or the server will die when clients disconnect suddenly */ 230 | ignore_signal(SIGPIPE); 231 | 232 | /* Load key database first */ 233 | keyserver.keydb = keydb_read(opts->filename); 234 | if (!keyserver.keydb) { 235 | log_msg(LLVL_FATAL, "Failed to load key database: %s", opts->filename); 236 | success = false; 237 | break; 238 | } 239 | 240 | if (!keyserver.keydb->server_database) { 241 | log_msg(LLVL_FATAL, "Not a server key database: %s", opts->filename); 242 | success = false; 243 | break; 244 | } 245 | 246 | if (keyserver.keydb->host_count == 0) { 247 | log_msg(LLVL_FATAL, "No host entries in key database: %s", opts->filename); 248 | success = false; 249 | break; 250 | } 251 | 252 | /* Then convert it into a vaulted key database */ 253 | keyserver.vaulted_keydb = vaulted_keydb_new(keyserver.keydb); 254 | if (!keyserver.vaulted_keydb) { 255 | log_msg(LLVL_FATAL, "Failed to create vaulted key database."); 256 | success = false; 257 | break; 258 | } 259 | 260 | if (!create_generic_tls_context(&keyserver.gctx, true)) { 261 | log_msg(LLVL_FATAL, "Failed to create OpenSSL server context."); 262 | success = false; 263 | break; 264 | } 265 | 266 | SSL_CTX_set_psk_find_session_callback(keyserver.gctx.ctx, psk_server_callback); 267 | 268 | keyserver.tcp_sd = create_tcp_server_socket(opts->port); 269 | if (keyserver.tcp_sd == -1) { 270 | log_msg(LLVL_ERROR, "Cannot start server without server socket."); 271 | success = false; 272 | break; 273 | } 274 | 275 | if (opts->answer_udp_queries) { 276 | keyserver.udp_sd = create_udp_socket(opts->port, false, 1000); 277 | if (keyserver.udp_sd == -1) { 278 | success = false; 279 | break; 280 | } 281 | 282 | struct udp_listen_thread_ctx_t udp_thread_ctx = { 283 | .keydb = keyserver.keydb, 284 | .udp_sd = keyserver.udp_sd, 285 | .port = keyserver.opts->port, 286 | }; 287 | if (!pthread_create_detached_thread(udp_handler_thread, &udp_thread_ctx, sizeof(udp_thread_ctx))) { 288 | log_libc(LLVL_FATAL, "Unable to create detached thread for UDP messages."); 289 | success = false; 290 | break; 291 | } 292 | } 293 | 294 | log_msg(LLVL_INFO, "Serving luksrku database for %u hosts.", keyserver.keydb->host_count); 295 | while (true) { 296 | struct sockaddr_in addr; 297 | unsigned int len = sizeof(addr); 298 | int client = accept(keyserver.tcp_sd, (struct sockaddr*)&addr, &len); 299 | if (client < 0) { 300 | log_libc(LLVL_ERROR, "Unable to accept(2)"); 301 | success = false; 302 | break; 303 | } 304 | 305 | /* Client has connected, fire up client thread. */ 306 | struct client_thread_ctx_t client_ctx = { 307 | .gctx = &keyserver.gctx, 308 | .keydb = keyserver.keydb, 309 | .vaulted_keydb = keyserver.vaulted_keydb, 310 | .fd = client, 311 | }; 312 | if (!pthread_create_detached_thread(client_handler_thread, &client_ctx, sizeof(client_ctx))) { 313 | log_libc(LLVL_FATAL, "Unable to create detached thread for client."); 314 | success = false; 315 | break; 316 | } 317 | } 318 | } while (false); 319 | if (keyserver.udp_sd != -1) { 320 | close(keyserver.udp_sd); 321 | } 322 | if (keyserver.tcp_sd != -1) { 323 | close(keyserver.tcp_sd); 324 | } 325 | free_generic_tls_context(&keyserver.gctx); 326 | vaulted_keydb_free(keyserver.vaulted_keydb); 327 | keydb_free(keyserver.keydb); 328 | return success; 329 | } 330 | -------------------------------------------------------------------------------- /keydb.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "keydb.h" 32 | #include "util.h" 33 | #include "uuid.h" 34 | #include "log.h" 35 | 36 | static unsigned int keydb_getsize_v3_hostcount(unsigned int host_count) { 37 | return sizeof(struct keydb_v3_t) + (host_count * sizeof(struct host_entry_v3_t)); 38 | } 39 | 40 | static unsigned int keydb_getsize_v3(const struct keydb_v3_t *keydb) { 41 | return keydb_getsize_v3_hostcount(keydb->host_count); 42 | } 43 | 44 | static unsigned int keydb_getsize_v2_hostcount(unsigned int host_count) { 45 | return sizeof(struct keydb_v2_t) + (host_count * sizeof(struct host_entry_v2_t)); 46 | } 47 | 48 | static unsigned int keydb_getsize_v2(const struct keydb_v2_t *keydb) { 49 | return keydb_getsize_v2_hostcount(keydb->host_count); 50 | } 51 | 52 | static unsigned int keydb_getsize_hostcount(unsigned int host_count) { 53 | return keydb_getsize_v3_hostcount(host_count); 54 | } 55 | 56 | static unsigned int keydb_getsize(const keydb_t *keydb) { 57 | return keydb_getsize_v3(keydb); 58 | } 59 | 60 | keydb_t* keydb_new(void) { 61 | keydb_t *keydb = calloc(sizeof(keydb_t), 1); 62 | keydb->common.keydb_version = KEYDB_CURRENT_VERSION; 63 | keydb->server_database = true; 64 | return keydb; 65 | } 66 | 67 | keydb_t* keydb_export_public(host_entry_t *host) { 68 | keydb_t *public_db = keydb_new(); 69 | if (!public_db) { 70 | return NULL; 71 | } 72 | public_db->server_database = false; 73 | 74 | if (!keydb_add_host(&public_db, host->host_name)) { 75 | keydb_free(public_db); 76 | return NULL; 77 | } 78 | 79 | /* Copy over whole entry */ 80 | host_entry_t *public_host = &public_db->hosts[0]; 81 | *public_host = *host; 82 | 83 | /* But remove all LUKS passphrases of course, this is for the luksrku client */ 84 | for (unsigned int i = 0; i < host->volume_count; i++) { 85 | volume_entry_t *volume = &public_host->volumes[i]; 86 | memset(volume->luks_passphrase_raw, 0, sizeof(volume->luks_passphrase_raw)); 87 | } 88 | 89 | return public_db; 90 | } 91 | 92 | void keydb_free(keydb_t *keydb) { 93 | if (keydb) { 94 | OPENSSL_cleanse(keydb, keydb_getsize(keydb)); 95 | free(keydb); 96 | } 97 | } 98 | 99 | volume_entry_t* keydb_get_volume_by_name(host_entry_t *host, const char *devmapper_name) { 100 | for (unsigned int i = 0; i < host->volume_count; i++) { 101 | volume_entry_t *volume = &host->volumes[i]; 102 | if (!strncasecmp(volume->devmapper_name, devmapper_name, sizeof(volume->devmapper_name) - 1)) { 103 | return volume; 104 | } 105 | } 106 | return NULL; 107 | } 108 | 109 | host_entry_t* keydb_get_host_by_name(keydb_t *keydb, const char *host_name) { 110 | for (unsigned int i = 0; i < keydb->host_count; i++) { 111 | host_entry_t *host = &keydb->hosts[i]; 112 | if (!strncasecmp(host->host_name, host_name, sizeof(host->host_name) - 1)) { 113 | return host; 114 | } 115 | } 116 | return NULL; 117 | } 118 | 119 | const volume_entry_t* keydb_get_volume_by_uuid(const host_entry_t *host, const uint8_t uuid[static 16]) { 120 | for (unsigned int i = 0; i < host->volume_count; i++) { 121 | const volume_entry_t *volume = &host->volumes[i]; 122 | if (!memcmp(volume->volume_uuid, uuid, 16)) { 123 | return volume; 124 | } 125 | } 126 | return NULL; 127 | } 128 | 129 | int keydb_get_host_index(const keydb_t *keydb, const host_entry_t *host) { 130 | int index = host - keydb->hosts; 131 | if (index < 0) { 132 | return -1; 133 | } else if ((unsigned int)index >= keydb ->host_count) { 134 | return -1; 135 | } 136 | return index; 137 | } 138 | 139 | int keydb_get_volume_index(const host_entry_t *host, const volume_entry_t *volume) { 140 | int index = volume - host->volumes; 141 | if (index < 0) { 142 | return -1; 143 | } else if ((unsigned int)index >= host->volume_count) { 144 | return -1; 145 | } 146 | return index; 147 | } 148 | 149 | const host_entry_t* keydb_get_host_by_uuid(const keydb_t *keydb, const uint8_t uuid[static 16]) { 150 | for (unsigned int i = 0; i < keydb->host_count; i++) { 151 | const host_entry_t *host = &keydb->hosts[i]; 152 | if (!memcmp(host->host_uuid, uuid, 16)) { 153 | return host; 154 | } 155 | } 156 | return NULL; 157 | } 158 | 159 | bool keydb_add_host(keydb_t **keydb, const char *host_name) { 160 | if (strlen(host_name) > MAX_HOST_NAME_LENGTH - 1) { 161 | log_msg(LLVL_ERROR, "Host name \"%s\" exceeds maximum length of %d characters.", host_name, MAX_HOST_NAME_LENGTH - 1); 162 | return false; 163 | } 164 | 165 | keydb_t *old_keydb = *keydb; 166 | if (keydb_get_host_by_name(old_keydb, host_name)) { 167 | log_msg(LLVL_ERROR, "Host name \"%s\" already present in key database.", host_name); 168 | return false; 169 | } 170 | 171 | keydb_t *new_keydb = realloc(old_keydb, keydb_getsize_hostcount(old_keydb->host_count + 1)); 172 | if (!new_keydb) { 173 | return false; 174 | } 175 | *keydb = new_keydb; 176 | 177 | host_entry_t *host = &new_keydb->hosts[new_keydb->host_count]; 178 | memset(host, 0, sizeof(host_entry_t)); 179 | if (!uuid_randomize(host->host_uuid)) { 180 | /* We keep the reallocation but do not increase the host count */ 181 | return false; 182 | } 183 | strncpy(host->host_name, host_name, sizeof(host->host_name) - 1); 184 | if (!keydb_rekey_host(host)) { 185 | /* We keep the reallocation but do not increase the host count */ 186 | return false; 187 | } 188 | 189 | new_keydb->host_count++; 190 | return true; 191 | } 192 | 193 | bool keydb_del_host_by_name(keydb_t **keydb, const char *host_name) { 194 | keydb_t *old_keydb = *keydb; 195 | host_entry_t *host = keydb_get_host_by_name(old_keydb, host_name); 196 | if (!host) { 197 | log_msg(LLVL_ERROR, "No such host: \"%s\"", host_name); 198 | return false; 199 | } 200 | 201 | int host_index = keydb_get_host_index(old_keydb, host); 202 | if (host_index < 0) { 203 | log_msg(LLVL_FATAL, "Fatal error determining host index for hostname \"%s\".", host_name); 204 | return false; 205 | } 206 | 207 | /* We keep the memory for now and do not realloc */ 208 | array_remove(old_keydb->hosts, sizeof(host_entry_t), old_keydb->host_count, host_index); 209 | old_keydb->host_count--; 210 | return true; 211 | } 212 | 213 | bool keydb_rekey_host(host_entry_t *host) { 214 | return buffer_randomize(host->tls_psk, sizeof(host->tls_psk)); 215 | } 216 | 217 | volume_entry_t* keydb_add_volume(host_entry_t *host, const char *devmapper_name, const uint8_t volume_uuid[static 16]) { 218 | if (strlen(devmapper_name) > MAX_DEVMAPPER_NAME_LENGTH - 1) { 219 | log_msg(LLVL_ERROR, "Device mapper name \"%s\" exceeds maximum length of %d characters.", devmapper_name, MAX_DEVMAPPER_NAME_LENGTH - 1); 220 | return false; 221 | } 222 | 223 | if (host->volume_count >= MAX_VOLUMES_PER_HOST) { 224 | log_msg(LLVL_ERROR, "Host \"%s\" already has maximum number of volumes (%d).", host->host_name, MAX_VOLUMES_PER_HOST); 225 | return NULL; 226 | } 227 | if (keydb_get_volume_by_name(host, devmapper_name)) { 228 | log_msg(LLVL_ERROR, "Volume name \"%s\" already present for host \"%s\" entry.", devmapper_name, host->host_name); 229 | return NULL; 230 | } 231 | 232 | volume_entry_t *volume = &host->volumes[host->volume_count]; 233 | memcpy(volume->volume_uuid, volume_uuid, 16); 234 | strncpy(volume->devmapper_name, devmapper_name, sizeof(volume->devmapper_name) - 1); 235 | if (!buffer_randomize(volume->luks_passphrase_raw, sizeof(volume->luks_passphrase_raw))) { 236 | log_msg(LLVL_ERROR, "Failed to produce %ld bytes of entropy for LUKS passphrase.", sizeof(volume->luks_passphrase_raw)); 237 | return NULL; 238 | } 239 | host->volume_count++; 240 | return volume; 241 | } 242 | 243 | bool keydb_del_volume(host_entry_t *host, const char *devmapper_name) { 244 | volume_entry_t *volume = keydb_get_volume_by_name(host, devmapper_name); 245 | if (!volume) { 246 | log_msg(LLVL_ERROR, "No such volume \"%s\" for host \"%s\".", devmapper_name, host->host_name); 247 | return false; 248 | } 249 | int index = keydb_get_volume_index(host, volume); 250 | if (index < 0) { 251 | log_msg(LLVL_FATAL, "Fatal error determining volume index of \"%s\" for host \"%s\".", devmapper_name, host->host_name); 252 | return false; 253 | } 254 | if (!array_remove(host->volumes, sizeof(volume_entry_t), host->volume_count, index)) { 255 | log_msg(LLVL_ERROR, "Failed to remove \"%s\" of host \"%s\".", devmapper_name, host->host_name); 256 | return false; 257 | } 258 | host->volume_count--; 259 | return true; 260 | } 261 | 262 | bool keydb_rekey_volume(volume_entry_t *volume) { 263 | return buffer_randomize(volume->luks_passphrase_raw, sizeof(volume->luks_passphrase_raw)); 264 | } 265 | 266 | bool keydb_volume_passphrase_present(const volume_entry_t *volume) { 267 | for (unsigned int i = 0; i < sizeof(volume->luks_passphrase_raw); i++) { 268 | if (volume->luks_passphrase_raw[i]) { 269 | return true; 270 | } 271 | } 272 | return false; 273 | } 274 | 275 | bool keydb_get_volume_luks_passphrase(const volume_entry_t *volume, char *dest, unsigned int dest_buffer_size) { 276 | return ascii_encode(dest, dest_buffer_size, volume->luks_passphrase_raw, sizeof(volume->luks_passphrase_raw)); 277 | } 278 | 279 | bool keydb_write(const keydb_t *keydb, const char *filename, const char *passphrase) { 280 | enum kdf_t kdf; 281 | if ((!passphrase) || (strlen(passphrase) == 0)) { 282 | /* For empty password, we can also use garbage KDF */ 283 | kdf = KDF_PBKDF2_SHA256_1000; 284 | } else { 285 | kdf = ENCRYPTED_FILE_DEFAULT_KDF; 286 | } 287 | return write_encrypted_file(filename, keydb, keydb_getsize(keydb), passphrase, kdf); 288 | } 289 | 290 | static bool passphrase_callback(char *buffer, unsigned int bufsize) { 291 | return query_passphrase("Database passphrase: ", buffer, bufsize); 292 | } 293 | 294 | static bool keydb_migrate_v2_to_v3(void **keydb_data, unsigned int *keydb_data_size) { 295 | log_msg(LLVL_INFO, "Migrating keydb version 2 to version 3"); 296 | struct keydb_v2_t *old_db = *((struct keydb_v2_t **)keydb_data); 297 | unsigned int new_db_size = keydb_getsize_v3_hostcount(old_db->host_count); 298 | struct keydb_v3_t *new_db = calloc(1, new_db_size); 299 | if (!new_db) { 300 | log_msg(LLVL_ERROR, "keydb migration failed to allocate %d bytes of memory", new_db_size); 301 | return false; 302 | } 303 | 304 | *new_db = (struct keydb_v3_t) { 305 | .common.keydb_version = 3, 306 | .server_database = old_db->server_database, 307 | .host_count = old_db->host_count, 308 | }; 309 | for (unsigned int i = 0; i < new_db->host_count; i++) { 310 | /* Do not copy over host_flags or volumes */ 311 | memcpy(&new_db->hosts[i], &old_db->hosts[i], sizeof(old_db->hosts[i]) - sizeof(old_db->hosts[i].volumes)); 312 | for (unsigned int j = 0; j < new_db->hosts[i].volume_count; j++) { 313 | /* Do not copy over volume_flags */ 314 | memcpy(&new_db->hosts[i].volumes[j], &old_db->hosts[i].volumes[j], sizeof(old_db->hosts[i].volumes[j])); 315 | } 316 | } 317 | 318 | OPENSSL_cleanse(old_db, *keydb_data_size); 319 | free(old_db); 320 | 321 | *keydb_data = new_db; 322 | *keydb_data_size = new_db_size; 323 | return true; 324 | } 325 | 326 | static keydb_t* keydb_migrate(void **keydb_data, unsigned int *keydb_data_size) { 327 | struct keydb_common_header_t *header; 328 | 329 | header = *((struct keydb_common_header_t**)keydb_data); 330 | if (header->keydb_version == 2) { 331 | if (*keydb_data_size != keydb_getsize_v2(*keydb_data)) { 332 | log_msg(LLVL_ERROR, "keydb version 2 has wrong size (%u bytes, but expected %u bytes).", *keydb_data_size, keydb_getsize_v2(*keydb_data)); 333 | return NULL; 334 | } 335 | if (!keydb_migrate_v2_to_v3(keydb_data, keydb_data_size)) { 336 | log_msg(LLVL_ERROR, "keydb version 2 to 3 migration failed."); 337 | return NULL; 338 | } 339 | } 340 | 341 | header = *((struct keydb_common_header_t**)keydb_data); 342 | if (header->keydb_version == 3) { 343 | if (*keydb_data_size != keydb_getsize_v3(*keydb_data)) { 344 | log_msg(LLVL_ERROR, "keydb version 3 has wrong size (%u bytes, but expected %u bytes).", *keydb_data_size, keydb_getsize_v3(*keydb_data)); 345 | return NULL; 346 | } 347 | } 348 | 349 | header = *((struct keydb_common_header_t**)keydb_data); 350 | if (header->keydb_version != KEYDB_CURRENT_VERSION) { 351 | log_msg(LLVL_ERROR, "keydb could be read, but is of version %u (we expected %u).", header->keydb_version, KEYDB_CURRENT_VERSION); 352 | return NULL; 353 | } 354 | return *((keydb_t**)keydb_data); 355 | 356 | } 357 | 358 | keydb_t* keydb_read(const char *filename) { 359 | struct decrypted_file_t decrypted_file = read_encrypted_file(filename, passphrase_callback); 360 | if (!decrypted_file.success) { 361 | return NULL; 362 | } 363 | 364 | keydb_t *keydb = keydb_migrate(&decrypted_file.data, &decrypted_file.data_length); 365 | if (!keydb) { 366 | OPENSSL_cleanse(decrypted_file.data, decrypted_file.data_length); 367 | free(decrypted_file.data); 368 | return NULL; 369 | } 370 | return keydb; 371 | } 372 | -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2016 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | 37 | #include "log.h" 38 | #include "openssl.h" 39 | #include "util.h" 40 | #include "msg.h" 41 | #include "client.h" 42 | #include "blacklist.h" 43 | #include "keydb.h" 44 | #include "uuid.h" 45 | #include "udp.h" 46 | #include "luks.h" 47 | 48 | struct keyclient_t { 49 | const struct pgmopts_client_t *opts; 50 | keydb_t *keydb; 51 | bool volume_unlocked[MAX_VOLUMES_PER_HOST]; 52 | unsigned char identifier[ASCII_UUID_BUFSIZE]; 53 | double broadcast_start_time; 54 | }; 55 | 56 | static int psk_client_callback(SSL *ssl, const EVP_MD *md, const unsigned char **id, size_t *idlen, SSL_SESSION **sessptr) { 57 | struct keyclient_t *key_client = (struct keyclient_t*)SSL_get_app_data(ssl); 58 | *id = key_client->identifier; 59 | *idlen = ASCII_UUID_CHARACTER_COUNT; 60 | 61 | return openssl_tls13_psk_establish_session(ssl, key_client->keydb->hosts[0].tls_psk, PSK_SIZE_BYTES, EVP_sha256(), sessptr); 62 | } 63 | 64 | static bool unlock_luks_volume(const volume_entry_t *volume, const struct msg_t *unlock_msg) { 65 | bool success = true; 66 | char luks_passphrase[LUKS_PASSPHRASE_TEXT_SIZE_BYTES]; 67 | if (ascii_encode(luks_passphrase, sizeof(luks_passphrase), unlock_msg->luks_passphrase_raw, sizeof(unlock_msg->luks_passphrase_raw))) { 68 | bool allow_discards = volume->volume_flags & VOLUME_FLAG_ALLOW_DISCARDS; 69 | success = open_luks_device(volume->volume_uuid, volume->devmapper_name, luks_passphrase, strlen(luks_passphrase), allow_discards); 70 | } else { 71 | log_msg(LLVL_FATAL, "Failed to transcribe raw LUKS passphrase to text form."); 72 | success = false; 73 | } 74 | OPENSSL_cleanse(luks_passphrase, sizeof(luks_passphrase)); 75 | return success; 76 | } 77 | 78 | static bool attempt_unlock_luks_volume(struct keyclient_t *keyclient, const struct msg_t *unlock_msg) { 79 | const host_entry_t *host = &keyclient->keydb->hosts[0]; 80 | const volume_entry_t* volume = keydb_get_volume_by_uuid(host, unlock_msg->volume_uuid); 81 | char volume_uuid_str[ASCII_UUID_BUFSIZE]; 82 | sprintf_uuid(volume_uuid_str, unlock_msg->volume_uuid); 83 | if (!volume) { 84 | log_msg(LLVL_WARNING, "Keyserver provided key for unlocking volume UUID %s, but this volume is not known on the client side.", volume_uuid_str); 85 | return false; 86 | } 87 | 88 | /* This is a valid volume which we need to unlock */ 89 | int volume_index = keydb_get_volume_index(host, volume); 90 | if (volume_index != -1) { 91 | if (keyclient->opts->no_luks) { 92 | keyclient->volume_unlocked[volume_index] = true; 93 | #ifdef DEBUG 94 | dump_hexline(stderr, "Raw key: ", unlock_msg->luks_passphrase_raw, LUKS_PASSPHRASE_RAW_SIZE_BYTES, false); 95 | #endif 96 | } else { 97 | if (!keyclient->volume_unlocked[volume_index]) { 98 | bool success = unlock_luks_volume(volume, unlock_msg); 99 | keyclient->volume_unlocked[volume_index] = success; 100 | if (!success) { 101 | log_msg(LLVL_ERROR, "Unlocking of volume %s / %s failed with the server-provided passphrase.", volume->devmapper_name, volume_uuid_str); 102 | return false; 103 | } 104 | } else { 105 | log_msg(LLVL_WARNING, "Volume %s / %s already unlocked, not attemping to unlock again.", volume->devmapper_name, volume_uuid_str); 106 | } 107 | } 108 | } else { 109 | log_msg(LLVL_FATAL, "Error calculating volume offset for volume %p from base %p.", volume, host->volumes); 110 | return false; 111 | } 112 | 113 | return true; 114 | } 115 | 116 | static bool contact_keyserver_socket(struct keyclient_t *keyclient, int sd) { 117 | struct generic_tls_ctx_t gctx; 118 | if (!create_generic_tls_context(&gctx, false)) { 119 | log_msg(LLVL_FATAL, "Failed to create OpenSSL client context."); 120 | return false; 121 | } 122 | SSL_CTX_set_psk_use_session_callback(gctx.ctx, psk_client_callback); 123 | 124 | SSL *ssl = SSL_new(gctx.ctx); 125 | if (ssl) { 126 | SSL_set_fd(ssl, sd); 127 | SSL_set_app_data(ssl, keyclient); 128 | 129 | if (SSL_connect(ssl) == 1) { 130 | struct msg_t msg; 131 | while (true) { 132 | int bytes_read = SSL_read(ssl, &msg, sizeof(msg)); 133 | if (bytes_read == 0) { 134 | /* Server closed the connection. */ 135 | break; 136 | } 137 | if (bytes_read != sizeof(msg)) { 138 | log_openssl(LLVL_FATAL, "SSL_read returned %d bytes when we expected to read %d", bytes_read, sizeof(msg)); 139 | break; 140 | } 141 | char uuid_str[ASCII_UUID_BUFSIZE]; 142 | sprintf_uuid(uuid_str, msg.volume_uuid); 143 | log_msg(LLVL_TRACE, "Received LUKS key to unlock volume with UUID %s", uuid_str); 144 | if (attempt_unlock_luks_volume(keyclient, &msg)) { 145 | log_msg(LLVL_DEBUG, "Successfully unlocked volume with UUID %s", uuid_str); 146 | } else { 147 | log_msg(LLVL_ERROR, "Failed to unlocked volume with UUID %s", uuid_str); 148 | } 149 | } 150 | OPENSSL_cleanse(&msg, sizeof(msg)); 151 | } else { 152 | log_openssl(LLVL_FATAL, "SSL_connect failed"); 153 | } 154 | 155 | } else { 156 | log_openssl(LLVL_FATAL, "Cannot establish SSL context when trying to connect to server"); 157 | } 158 | 159 | SSL_free(ssl); 160 | free_generic_tls_context(&gctx); 161 | return true; 162 | } 163 | 164 | static bool contact_keyserver_ipv4(struct keyclient_t *keyclient, struct sockaddr_in *sockaddr_in, unsigned int port) { 165 | sockaddr_in->sin_port = htons(port); 166 | 167 | int sd = socket(sockaddr_in->sin_family, SOCK_STREAM, 0); 168 | if (sd == -1) { 169 | log_libc(LLVL_ERROR, "Failed to create socket(3)"); 170 | return false; 171 | } 172 | 173 | if (connect(sd, (struct sockaddr*)sockaddr_in, sizeof(struct sockaddr_in)) == -1) { 174 | log_libc(LLVL_ERROR, "Failed to connect(3) to %d.%d.%d.%d:%d", PRINTF_FORMAT_IP(sockaddr_in), port); 175 | close(sd); 176 | return false; 177 | } 178 | 179 | bool success = contact_keyserver_socket(keyclient, sd); 180 | 181 | shutdown(sd, SHUT_RDWR); 182 | close(sd); 183 | return success; 184 | } 185 | 186 | static bool contact_keyserver_hostname(struct keyclient_t *keyclient, const char *hostname) { 187 | struct addrinfo hints = { 188 | .ai_family = AF_INET, 189 | .ai_socktype = SOCK_STREAM, 190 | }; 191 | struct addrinfo *result; 192 | int resolve_result = getaddrinfo(hostname, NULL, &hints, &result); 193 | if (resolve_result) { 194 | log_msg(LLVL_ERROR, "Failed to resolve hostname %s using getaddrinfo(3): %s", hostname, gai_strerror(resolve_result)); 195 | return false; 196 | } 197 | 198 | if (result->ai_addr->sa_family != AF_INET) { 199 | freeaddrinfo(result); 200 | log_msg(LLVL_ERROR, "getaddrinfo(3) returned non-IPv4 entry"); 201 | return false; 202 | } 203 | 204 | struct sockaddr_in *sin_address = (struct sockaddr_in*)result->ai_addr; 205 | log_msg(LLVL_TRACE, "Resolved %s to %d.%d.%d.%d", hostname, PRINTF_FORMAT_IP(sin_address)); 206 | 207 | bool success = contact_keyserver_ipv4(keyclient, sin_address, keyclient->opts->port); 208 | 209 | freeaddrinfo(result); 210 | return success; 211 | } 212 | 213 | static unsigned int locked_volume_count(struct keyclient_t *keyclient) { 214 | unsigned int count = 0; 215 | const unsigned int volume_count = keyclient->keydb->hosts[0].volume_count; 216 | for (unsigned int i = 0; i < volume_count; i++) { 217 | if (!keyclient->volume_unlocked[i]) { 218 | count++; 219 | } 220 | } 221 | return count; 222 | } 223 | 224 | static bool all_volumes_unlocked(struct keyclient_t *keyclient) { 225 | return locked_volume_count(keyclient) == 0; 226 | } 227 | 228 | static unsigned int determine_timeout(struct keyclient_t *keyclient) { 229 | unsigned int client_timeout_secs = 0; 230 | if (keyclient->opts->timeout_seconds) { 231 | /* Command line always has precedence */ 232 | client_timeout_secs = keyclient->opts->timeout_seconds; 233 | } else { 234 | /* Alternatively, take the one in the configuration file */ 235 | client_timeout_secs = keyclient->keydb->hosts[0].client_default_timeout_secs; 236 | } 237 | return client_timeout_secs; 238 | } 239 | 240 | static bool abort_searching_for_keyserver(struct keyclient_t *keyclient) { 241 | if (all_volumes_unlocked(keyclient)) { 242 | log_msg(LLVL_DEBUG, "All volumes unlocked successfully."); 243 | return true; 244 | } 245 | 246 | unsigned int client_timeout_secs = determine_timeout(keyclient); 247 | if (client_timeout_secs) { 248 | double time_passed = now() - keyclient->broadcast_start_time; 249 | if (time_passed >= client_timeout_secs) { 250 | log_msg(LLVL_WARNING, "Could not unlock all volumes after %u seconds, giving up. %d volumes still locked.", client_timeout_secs, locked_volume_count(keyclient)); 251 | return true; 252 | } 253 | } 254 | 255 | return false; 256 | } 257 | 258 | static bool broadcast_for_keyserver(struct keyclient_t *keyclient) { 259 | { 260 | unsigned int client_timeout_secs = determine_timeout(keyclient); 261 | if (client_timeout_secs) { 262 | log_msg(LLVL_DEBUG, "Searching luksrku keyserver, will give up after %u seconds", client_timeout_secs); 263 | } else { 264 | log_msg(LLVL_DEBUG, "Searching luksrku keyserver, will not give up until all volumes unlocked"); 265 | } 266 | } 267 | 268 | int sd = create_udp_socket(0, true, 1000); 269 | if (sd == -1) { 270 | return false; 271 | } 272 | 273 | 274 | keyclient->broadcast_start_time = now(); 275 | struct udp_query_t query; 276 | memcpy(query.magic, UDP_MESSAGE_MAGIC, sizeof(query.magic)); 277 | memcpy(query.host_uuid, keyclient->keydb->hosts[0].host_uuid, 16); 278 | while (true) { 279 | log_msg(LLVL_TRACE, "Broadcasting search for luksrku keyserver"); 280 | send_udp_broadcast_message(sd, keyclient->opts->port, &query, sizeof(query)); 281 | 282 | struct sockaddr_in src = { 283 | .sin_family = AF_INET, 284 | .sin_port = htons(keyclient->opts->port), 285 | .sin_addr.s_addr = htonl(INADDR_ANY), 286 | }; 287 | struct udp_response_t response; 288 | if (wait_udp_response(sd, &response, &src)) { 289 | if (!is_ip_blacklisted(src.sin_addr.s_addr)) { 290 | log_msg(LLVL_INFO, "Keyserver found at %d.%d.%d.%d", PRINTF_FORMAT_IP(&src)); 291 | blacklist_ip(src.sin_addr.s_addr, BLACKLIST_TIMEOUT_CLIENT); 292 | if (!contact_keyserver_ipv4(keyclient, &src, keyclient->opts->port)) { 293 | log_msg(LLVL_WARNING, "Keyserver announced at %d.%d.%d.%d, but connection to it failed.", PRINTF_FORMAT_IP(&src)); 294 | } 295 | } else { 296 | log_msg(LLVL_DEBUG, "Potential keyserver at %d.%d.%d.%d ignored, blacklist in effect.", PRINTF_FORMAT_IP(&src)); 297 | } 298 | } 299 | 300 | if (abort_searching_for_keyserver(keyclient)) { 301 | break; 302 | } 303 | } 304 | return true; 305 | } 306 | 307 | bool keyclient_start(const struct pgmopts_client_t *opts) { 308 | /* Load key database first */ 309 | struct keyclient_t keyclient = { 310 | .opts = opts, 311 | }; 312 | bool success = true; 313 | 314 | do { 315 | keyclient.keydb = keydb_read(opts->filename); 316 | if (!keyclient.keydb) { 317 | log_msg(LLVL_FATAL, "Failed to load key database: %s", opts->filename); 318 | success = false; 319 | break; 320 | } 321 | 322 | if (keyclient.keydb->server_database) { 323 | log_msg(LLVL_FATAL, "Not an exported key database: %s -- this database contains LUKS passphrases, refusing to work with it!", opts->filename); 324 | success = false; 325 | break; 326 | } 327 | 328 | if (keyclient.keydb->host_count != 1) { 329 | log_msg(LLVL_FATAL, "Host count %d in %s -- expected exactly one host entry for an exported database.", keyclient.keydb->host_count, opts->filename); 330 | success = false; 331 | break; 332 | } 333 | 334 | host_entry_t *host = &keyclient.keydb->hosts[0]; 335 | if (host->volume_count == 0) { 336 | log_msg(LLVL_FATAL, "No volumes found in exported database %s.", opts->filename); 337 | success = false; 338 | break; 339 | } 340 | 341 | /* Determine which of these volumes are already unlocked */ 342 | for (unsigned int i = 0; i < host->volume_count; i++) { 343 | keyclient.volume_unlocked[i] = is_luks_device_opened(host->volumes[i].devmapper_name); 344 | } 345 | if ((!pgmopts->client.force_unlock_all) && all_volumes_unlocked(&keyclient)) { 346 | log_msg(LLVL_INFO, "All %u volumes are unlocked already, not contacting luksrku key server.", host->volume_count); 347 | break; 348 | } else { 349 | log_msg(LLVL_DEBUG, "%u of %u volumes are currently locked.", locked_volume_count(&keyclient), host->volume_count); 350 | } 351 | 352 | /* Transcribe the host UUID to ASCII so we only have to do this once */ 353 | sprintf_uuid((char*)keyclient.identifier, host->host_uuid); 354 | 355 | if (opts->hostname) { 356 | if (!contact_keyserver_hostname(&keyclient, opts->hostname)) { 357 | log_msg(LLVL_ERROR, "Failed to contact key server: %s", opts->hostname); 358 | success = false; 359 | break; 360 | } 361 | } else { 362 | if (!broadcast_for_keyserver(&keyclient)) { 363 | log_msg(LLVL_ERROR, "Failed to find key server using UDP broadcast."); 364 | success = false; 365 | break; 366 | } 367 | } 368 | } while (false); 369 | 370 | if (keyclient.keydb) { 371 | keydb_free(keyclient.keydb); 372 | } 373 | return success; 374 | } 375 | -------------------------------------------------------------------------------- /file_encryption.c: -------------------------------------------------------------------------------- 1 | /* 2 | luksrku - Tool to remotely unlock LUKS disks using TLS. 3 | Copyright (C) 2016-2019 Johannes Bauer 4 | 5 | This file is part of luksrku. 6 | 7 | luksrku is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; this program is ONLY licensed under 10 | version 3 of the License, later versions are explicitly excluded. 11 | 12 | luksrku is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with luksrku; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | Johannes Bauer 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | #include "openssl.h" 34 | #include "file_encryption.h" 35 | #include "log.h" 36 | #include "util.h" 37 | #include "global.h" 38 | 39 | struct key_t { 40 | char passphrase[MAX_PASSPHRASE_LENGTH]; 41 | enum kdf_t kdf; 42 | uint8_t salt[ENCRYPTED_FILE_SALT_SIZE]; 43 | uint8_t key[ENCRYPTED_FILE_KEY_SIZE]; 44 | }; 45 | 46 | #ifdef DEBUG 47 | static void dump_key(const struct key_t *key) { 48 | fprintf(stderr, "Dumping key:\n"); 49 | fprintf(stderr, " Passphrase : %s\n", key->passphrase); 50 | fprintf(stderr, " Salt : "); 51 | dump_hex(stderr, key->salt, ENCRYPTED_FILE_SALT_SIZE, false); 52 | fprintf(stderr, "\n"); 53 | fprintf(stderr, " Derived key: "); 54 | dump_hex(stderr, key->key, ENCRYPTED_FILE_KEY_SIZE, false); 55 | fprintf(stderr, "\n"); 56 | } 57 | #endif 58 | 59 | 60 | /* Derives a previous key with known salt. Passphrase and salt must be set. */ 61 | static bool derive_previous_key(struct key_t *key) { 62 | const unsigned int pwlen = strlen(key->passphrase); 63 | 64 | if ((key->kdf >= KDF_SCRYPT_MIN) && (key->kdf <= KDF_SCRYPT_MAX)) { 65 | unsigned int N, r, p; 66 | switch (key->kdf) { 67 | case KDF_SCRYPT_N17_r8_p1: 68 | N = 1 << 17; 69 | r = 8; 70 | p = 1; 71 | break; 72 | 73 | case KDF_SCRYPT_N18_r8_p1: 74 | N = 1 << 18; 75 | r = 8; 76 | p = 1; 77 | break; 78 | 79 | default: 80 | log_msg(LLVL_FATAL, "Fatal: unknown scrypt key derivation function (0x%x)", key->kdf); 81 | return false; 82 | } 83 | 84 | const unsigned int maxalloc_mib = 8 + ((128 * N * r * p + (1024 * 1024 - 1)) / 1024 / 1024); 85 | log_msg(LLVL_DEBUG, "Deriving scrypt key with N = %u, r = %u, p = %u, i.e., ~%u MiB of memory", N, r, p, maxalloc_mib); 86 | 87 | int result = EVP_PBE_scrypt(key->passphrase, pwlen, (const unsigned char*)key->salt, ENCRYPTED_FILE_SALT_SIZE, N, r, p, maxalloc_mib * 1024 * 1024, key->key, ENCRYPTED_FILE_KEY_SIZE); 88 | if (result != 1) { 89 | log_msg(LLVL_FATAL, "Fatal: key derivation using scrypt failed"); 90 | return false; 91 | } 92 | } else if ((key->kdf >= KDF_PBKDF2_MIN) && (key->kdf <= KDF_PBKDF2_MAX)) { 93 | unsigned int iterations; 94 | switch (key->kdf) { 95 | case KDF_PBKDF2_SHA256_1000: 96 | iterations = 1000; 97 | break; 98 | 99 | default: 100 | log_msg(LLVL_FATAL, "Fatal: unknown PBKDF2 key derivation function (0x%x)", key->kdf); 101 | return false; 102 | } 103 | 104 | int result = PKCS5_PBKDF2_HMAC(key->passphrase, pwlen, (const unsigned char*)key->salt, ENCRYPTED_FILE_SALT_SIZE, iterations, EVP_sha256(), ENCRYPTED_FILE_KEY_SIZE, key->key); 105 | if (result != 1) { 106 | log_msg(LLVL_FATAL, "Fatal: key derivation using PBKDF2 failed"); 107 | return false; 108 | } 109 | } else { 110 | log_msg(LLVL_FATAL, "Fatal: unknown key derivation function (0x%x)", key->kdf); 111 | return false; 112 | } 113 | #ifdef DEBUG 114 | dump_key(key); 115 | #endif 116 | return true; 117 | } 118 | 119 | static bool encrypt_aes256_gcm(const void *plaintext, unsigned int plaintext_len, unsigned char *key, unsigned char *iv, unsigned char *ciphertext, unsigned char *tag) { 120 | bool success = true; 121 | log_msg(LLVL_DEBUG, "Encrypting %u bytes of plaintext using AES256-GCM", plaintext_len); 122 | 123 | EVP_CIPHER_CTX *ctx = NULL; 124 | do { 125 | /* Create and initialise the context */ 126 | ctx = EVP_CIPHER_CTX_new(); 127 | if (!ctx) { 128 | log_openssl(LLVL_FATAL, "Cannot create EVP_CIPHER_CTX for encryption"); 129 | success = false; 130 | break; 131 | } 132 | if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) { 133 | log_openssl(LLVL_FATAL, "Error in EVP_EncryptInit_ex"); 134 | success = false; 135 | break; 136 | } 137 | 138 | /* Set IV length to 128 bit */ 139 | if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL)) { 140 | log_openssl(LLVL_FATAL, "Error setting IV length for encryption"); 141 | success = false; 142 | break; 143 | } 144 | 145 | /* Initialise key and IV */ 146 | if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) { 147 | log_openssl(LLVL_FATAL, "Error setting encryption key and IV"); 148 | success = false; 149 | break; 150 | } 151 | 152 | /* Provide the message to be encrypted, and obtain the encrypted output. */ 153 | int ciphertext_len = 0; 154 | if (!EVP_EncryptUpdate(ctx, ciphertext, &ciphertext_len, plaintext, plaintext_len)) { 155 | log_openssl(LLVL_FATAL, "Error encrypting data"); 156 | success = false; 157 | break; 158 | } 159 | if (ciphertext_len != (int)plaintext_len) { 160 | log_openssl(LLVL_FATAL, "Unexpected deviation from plaintext length (%d bytes) to ciphertext length (%d bytes) during encryption", plaintext_len, ciphertext_len); 161 | success = false; 162 | break; 163 | } 164 | 165 | /* Finalise the encryption. Normally ciphertext bytes may be written at 166 | * this stage, but this does not occur in GCM mode. */ 167 | int padding_len = 0; 168 | if (!EVP_EncryptFinal_ex(ctx, ciphertext + ciphertext_len, &padding_len)) { 169 | log_openssl(LLVL_FATAL, "Finalization of encryption failed."); 170 | success = false; 171 | break; 172 | } 173 | if (padding_len != 0) { 174 | log_openssl(LLVL_FATAL, "Unexpected deviation from expected padding length (got %d bytes) during encryption", padding_len); 175 | success = false; 176 | break; 177 | } 178 | 179 | /* Get the authentication tag */ 180 | if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag)) { 181 | log_openssl(LLVL_FATAL, "Failed to retrieve authentication tag"); 182 | success = false; 183 | break; 184 | } 185 | } while (false); 186 | 187 | /* Clean up */ 188 | if (ctx) { 189 | EVP_CIPHER_CTX_free(ctx); 190 | } 191 | return success; 192 | } 193 | 194 | static bool decrypt_aes256_gcm(unsigned char *ciphertext, unsigned int ciphertext_len, unsigned char *tag, unsigned char *key, unsigned char *iv, void *plaintext) { 195 | bool success = true; 196 | log_msg(LLVL_DEBUG, "Decrypting %u bytes of ciphertext using AES256-GCM", ciphertext_len); 197 | 198 | EVP_CIPHER_CTX *ctx = NULL; 199 | do { 200 | /* Create and initialise the context */ 201 | ctx = EVP_CIPHER_CTX_new(); 202 | if (!ctx) { 203 | log_openssl(LLVL_FATAL, "Cannot create EVP_CIPHER_CTX for decryption"); 204 | success = false; 205 | break; 206 | } 207 | 208 | if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) { 209 | log_openssl(LLVL_FATAL, "Error in EVP_DecryptInit_ex"); 210 | success = false; 211 | break; 212 | } 213 | 214 | /* Set IV length. Not necessary if this is 12 bytes (96 bits) */ 215 | if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL)) { 216 | log_openssl(LLVL_FATAL, "Error setting IV length for decryption"); 217 | success = false; 218 | break; 219 | } 220 | 221 | /* Initialise key and IV */ 222 | if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) { 223 | log_openssl(LLVL_FATAL, "Error setting decryption key and IV"); 224 | success = false; 225 | break; 226 | } 227 | 228 | /* Provide the message to be decrypted, and obtain the plaintext output. 229 | * EVP_DecryptUpdate can be called multiple times if necessary 230 | */ 231 | int plaintext_len = 0; 232 | if (!EVP_DecryptUpdate(ctx, plaintext, &plaintext_len, ciphertext, ciphertext_len)) { 233 | log_openssl(LLVL_FATAL, "Error decrypting data"); 234 | success = false; 235 | break; 236 | } 237 | if (plaintext_len != (int)ciphertext_len) { 238 | log_openssl(LLVL_FATAL, "Unexpected deviation from plaintext length (%d bytes) to ciphertext length (%d bytes) during decryption", plaintext_len, ciphertext_len); 239 | success = false; 240 | break; 241 | } 242 | 243 | /* Set expected tag value. Works in OpenSSL 1.0.1d and later */ 244 | if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag)) { 245 | log_openssl(LLVL_FATAL, "Error setting authentication tag length"); 246 | success = false; 247 | break; 248 | } 249 | 250 | /* Finalise the decryption. A positive return value indicates success, 251 | * anything else is a failure - the plaintext is not trustworthy. */ 252 | int padding_len = 0; 253 | if (EVP_DecryptFinal_ex(ctx, (uint8_t*)plaintext + plaintext_len, &padding_len) <= 0) { 254 | log_openssl(LLVL_FATAL, "Finalization of decryption failed; likely authentication tag mismatch."); 255 | success = false; 256 | break; 257 | } 258 | if (padding_len != 0) { 259 | log_openssl(LLVL_FATAL, "Unexpected deviation from expected padding length (got %d bytes) during decryption", padding_len); 260 | success = false; 261 | break; 262 | } 263 | } while (false); 264 | 265 | /* Clean up */ 266 | if (ctx) { 267 | EVP_CIPHER_CTX_free(ctx); 268 | } 269 | return success; 270 | } 271 | 272 | /* Generates a random salt and derives a new key. Passphrase must be set. */ 273 | static bool derive_new_key(struct key_t *key) { 274 | if (!RAND_bytes(key->salt, ENCRYPTED_FILE_SALT_SIZE)) { 275 | log_msg(LLVL_FATAL, "Cannot get salt entropy from RAND_bytes()"); 276 | return false; 277 | } 278 | return derive_previous_key(key); 279 | } 280 | 281 | struct decrypted_file_t read_encrypted_file(const char *filename, passphrase_callback_function_t passphrase_callback) { 282 | struct decrypted_file_t result = { 283 | .success = true, 284 | .data = NULL, 285 | }; 286 | struct encrypted_file_t *encrypted_file = NULL; 287 | struct key_t key = { 0 }; 288 | 289 | do { 290 | /* Stat the file first to find out the size */ 291 | struct stat statbuf; 292 | if (stat(filename, &statbuf) == -1) { 293 | log_libc(LLVL_ERROR, "stat of %s failed", filename); 294 | result.success = false; 295 | break; 296 | } 297 | 298 | /* Check if the file is long enough to be an encrypted file */ 299 | const unsigned int encrypted_file_size = statbuf.st_size; 300 | if (encrypted_file_size < sizeof(struct encrypted_file_t)) { 301 | log_msg(LLVL_ERROR, "%s: too small to be encrypted file (%u bytes)", filename, encrypted_file_size); 302 | result.success = false; 303 | break; 304 | } 305 | 306 | /* Now allocate memory for plain- and ciphertext */ 307 | encrypted_file = malloc(encrypted_file_size); 308 | if (!encrypted_file) { 309 | log_libc(LLVL_ERROR, "malloc(3) of encrypted file (%u bytes) failed", encrypted_file_size); 310 | result.success = false; 311 | break; 312 | } 313 | 314 | const unsigned int ciphertext_size = encrypted_file_size - sizeof(struct encrypted_file_t); 315 | const unsigned int plaintext_size = ciphertext_size; 316 | result.data_length = plaintext_size; 317 | result.data = malloc(plaintext_size); 318 | if (!result.data) { 319 | log_libc(LLVL_ERROR, "malloc(3) of plaintext (%u bytes) failed", plaintext_size); 320 | result.success = false; 321 | break; 322 | } 323 | 324 | /* Read in the encrypted file */ 325 | FILE *f = fopen(filename, "r"); 326 | if (!f) { 327 | log_libc(LLVL_ERROR, "fopen"); 328 | result.success = false; 329 | break; 330 | } 331 | if (fread(encrypted_file, encrypted_file_size, 1, f) != 1) { 332 | log_libc(LLVL_ERROR, "fread"); 333 | result.success = false; 334 | fclose(f); 335 | break; 336 | } 337 | fclose(f); 338 | 339 | /* Copy the file's salt into the key structure so we can derive the 340 | * proper decryption key */ 341 | memcpy(key.salt, encrypted_file->salt, ENCRYPTED_FILE_IV_SIZE); 342 | key.kdf = encrypted_file->kdf; 343 | 344 | /* Get the passphrase from the user (if it is protected with one) */ 345 | if (encrypted_file->empty_passphrase) { 346 | key.passphrase[0] = 0; 347 | } else { 348 | if (!passphrase_callback) { 349 | log_msg(LLVL_FATAL, "No passphrase callback given, but input file requires one."); 350 | result.success = false; 351 | break; 352 | } 353 | if (!passphrase_callback(key.passphrase, sizeof(key.passphrase))) { 354 | log_msg(LLVL_FATAL, "Failed to query passphrase."); 355 | result.success = false; 356 | break; 357 | } 358 | } 359 | 360 | /* Then derive the key */ 361 | if (!derive_previous_key(&key)) { 362 | log_msg(LLVL_FATAL, "Key derivation failed."); 363 | result.success = false; 364 | break; 365 | } 366 | 367 | /* Then do the decryption and check if authentication is OK */ 368 | bool decryption_successful = decrypt_aes256_gcm(encrypted_file->ciphertext, ciphertext_size, encrypted_file->auth_tag, key.key, encrypted_file->iv, result.data); 369 | if (!decryption_successful) { 370 | log_msg(LLVL_FATAL, "Decryption error. Wrong passphrase or given file corrupt."); 371 | result.success = false; 372 | break; 373 | } 374 | } while (false); 375 | 376 | OPENSSL_cleanse(&key, sizeof(key)); 377 | if (!result.success) { 378 | if (result.data) { 379 | OPENSSL_cleanse(result.data, result.data_length); 380 | free(result.data); 381 | } 382 | result.data = NULL; 383 | result.data_length = 0; 384 | } 385 | if (encrypted_file) { 386 | free(encrypted_file); 387 | } 388 | return result; 389 | } 390 | 391 | bool write_encrypted_file(const char *filename, const void *plaintext, unsigned int plaintext_length, const char *passphrase, enum kdf_t kdf) { 392 | struct key_t key = { 393 | .kdf = kdf, 394 | }; 395 | strncpy(key.passphrase, passphrase, sizeof(key.passphrase) - 1); 396 | if (!derive_new_key(&key)) { 397 | log_msg(LLVL_FATAL, "Key derivation failed."); 398 | return false; 399 | } 400 | 401 | /* Allocate memory for plain- and ciphertext */ 402 | const unsigned int ciphertext_length = plaintext_length; 403 | const unsigned int encrypted_file_size = sizeof(struct encrypted_file_t) + ciphertext_length; 404 | struct encrypted_file_t *encrypted_file = calloc(1, encrypted_file_size); 405 | if (!encrypted_file) { 406 | log_libc(LLVL_FATAL, "malloc(3) of encrypted_file failed"); 407 | OPENSSL_cleanse(&key, sizeof(key)); 408 | return false; 409 | } 410 | 411 | /* Initialize encrypted file structure */ 412 | encrypted_file->empty_passphrase = (strlen(key.passphrase) == 0) ? 1 : 0; 413 | encrypted_file->kdf = key.kdf; 414 | memcpy(encrypted_file->salt, key.salt, ENCRYPTED_FILE_SALT_SIZE); 415 | 416 | /* Randomize encrypting IV */ 417 | if (RAND_bytes(encrypted_file->iv, ENCRYPTED_FILE_IV_SIZE) != 1) { 418 | log_openssl(LLVL_FATAL, "Failed to get entropy from RAND_bytes for IV"); 419 | OPENSSL_cleanse(&key, sizeof(key)); 420 | free(encrypted_file); 421 | return false; 422 | } 423 | 424 | /* Encrypt and authenticate plaintext */ 425 | if (!encrypt_aes256_gcm(plaintext, plaintext_length, key.key, encrypted_file->iv, encrypted_file->ciphertext, encrypted_file->auth_tag)) { 426 | log_libc(LLVL_FATAL, "encryption failed"); 427 | OPENSSL_cleanse(&key, sizeof(key)); 428 | free(encrypted_file); 429 | return false; 430 | } 431 | 432 | /* Destroy derived key */ 433 | OPENSSL_cleanse(&key, sizeof(key)); 434 | 435 | /* Write encrypted data to file */ 436 | FILE *f = fopen(filename, "w"); 437 | bool success = true; 438 | if (f) { 439 | if (fwrite(encrypted_file, encrypted_file_size, 1, f) != 1) { 440 | log_libc(LLVL_ERROR, "fwrite(3) into %s failed", filename); 441 | success = false; 442 | } 443 | fclose(f); 444 | } else { 445 | log_libc(LLVL_ERROR, "fopen(3) of %s failed", filename); 446 | success = false; 447 | } 448 | 449 | free(encrypted_file); 450 | return success; 451 | } 452 | --------------------------------------------------------------------------------