diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 17:44:12 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 17:44:12 +0000 |
commit | 1be69c2c660b70ac2f4de2a5326e27e3e60eb82d (patch) | |
tree | bb299ab6f411f4fccd735907035de710e4ec6abc /misc | |
parent | Initial commit. (diff) | |
download | cryptsetup-upstream/2%2.3.7.tar.xz cryptsetup-upstream/2%2.3.7.zip |
Adding upstream version 2:2.3.7.upstream/2%2.3.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'misc')
-rw-r--r-- | misc/11-dm-crypt.rules | 17 | ||||
-rw-r--r-- | misc/dict_search/Makefile | 17 | ||||
-rw-r--r-- | misc/dict_search/README | 22 | ||||
-rw-r--r-- | misc/dict_search/crypt_dict.c | 158 | ||||
-rw-r--r-- | misc/dracut_90reencrypt/README | 40 | ||||
-rwxr-xr-x | misc/dracut_90reencrypt/check.old | 5 | ||||
-rwxr-xr-x | misc/dracut_90reencrypt/install.old | 6 | ||||
-rwxr-xr-x | misc/dracut_90reencrypt/module-setup.sh | 32 | ||||
-rwxr-xr-x | misc/dracut_90reencrypt/parse-reencrypt.sh | 38 | ||||
-rwxr-xr-x | misc/dracut_90reencrypt/reencrypt-verbose.sh | 6 | ||||
-rwxr-xr-x | misc/dracut_90reencrypt/reencrypt.sh | 84 | ||||
-rw-r--r-- | misc/keyslot_checker/Makefile | 14 | ||||
-rw-r--r-- | misc/keyslot_checker/README | 120 | ||||
-rw-r--r-- | misc/keyslot_checker/chk_luks_keyslots.c | 371 | ||||
-rwxr-xr-x | misc/luks-header-from-active | 59 | ||||
-rw-r--r-- | misc/luks2_keyslot_example/Makefile | 24 | ||||
-rw-r--r-- | misc/luks2_keyslot_example/README | 3 | ||||
-rw-r--r-- | misc/luks2_keyslot_example/keyslot_test.c | 409 | ||||
-rw-r--r-- | misc/luks2_keyslot_example/keyslot_test_remote_pass.c | 264 |
19 files changed, 1689 insertions, 0 deletions
diff --git a/misc/11-dm-crypt.rules b/misc/11-dm-crypt.rules new file mode 100644 index 0000000..dfbb3a0 --- /dev/null +++ b/misc/11-dm-crypt.rules @@ -0,0 +1,17 @@ +# Old udev rules historically used in device-mapper. +# No need to install these until you have some weird configuration. +# (Code internally set the same flags.) + +ACTION!="add|change", GOTO="crypt_end" +ENV{DM_UDEV_RULES_VSN}!="?*", GOTO="crypt_end" + +ENV{DM_UUID}=="CRYPT-TEMP-?*", GOTO="crypt_disable" +ENV{DM_UUID}!="?*", ENV{DM_NAME}=="temporary-cryptsetup-?*", GOTO="crypt_disable" +GOTO="crypt_end" + +LABEL="crypt_disable" +ENV{DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG}="1" +ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}="1" +ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1" + +LABEL="crypt_end" diff --git a/misc/dict_search/Makefile b/misc/dict_search/Makefile new file mode 100644 index 0000000..0226c98 --- /dev/null +++ b/misc/dict_search/Makefile @@ -0,0 +1,17 @@ +TARGET=crypt_dict +CFLAGS=-O2 -g -Wall -D_GNU_SOURCE +LDLIBS=-lcryptsetup +CC=gcc + +SOURCES=$(wildcard *.c) +OBJECTS=$(SOURCES:.c=.o) + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) -o $@ $^ $(LDLIBS) + +clean: + rm -f *.o *~ core $(TARGET) + +.PHONY: clean diff --git a/misc/dict_search/README b/misc/dict_search/README new file mode 100644 index 0000000..fc6aa44 --- /dev/null +++ b/misc/dict_search/README @@ -0,0 +1,22 @@ +Simple example how to use libcryptsetup +for password search. + +Run: crypt_dict luks|tcrypt <device|image> <dictionary> [cpus] + +luks|tcrypt specified device type (LUKS or TrueCrypt) + +<device|image> is LUKS or TrueCrypt device or image + +<dictionary> is list of passphrases to try +(note trailing EOL is stripped) + +cpus - number of processes to start in parallel + +Format of dictionary file is simple one password per line, +if first char on line is # it is skipped as comment. + +For LUKS, you have it run as root (device-mapper cannot +create dmcrypt devices as nrmal user. Code need +to map keyslots as temporary dmcrypt device.) + +For TrueCrypt devices root privilege is not required. diff --git a/misc/dict_search/crypt_dict.c b/misc/dict_search/crypt_dict.c new file mode 100644 index 0000000..c80d502 --- /dev/null +++ b/misc/dict_search/crypt_dict.c @@ -0,0 +1,158 @@ +/* + * Example of LUKS/TrueCrypt password dictionary search + * + * Copyright (C) 2012 Milan Broz <gmazyland@gmail.com> + * + * Run this (for LUKS as root), + * e.g. ./crypt_dict test.img /usr/share/john/password.lst 4 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/prctl.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <libcryptsetup.h> + +#define MAX_LEN 512 + +static enum { LUKS, TCRYPT } device_type; + +static void check(struct crypt_device *cd, const char *pwd_file, unsigned my_id, unsigned max_id) +{ + FILE *f; + int len, r = -1; + unsigned long line = 0; + char pwd[MAX_LEN]; + + if (fork()) + return; + + /* open password file, now in separate process */ + f = fopen(pwd_file, "r"); + if (!f) { + printf("Cannot open %s.\n", pwd_file); + exit(EXIT_FAILURE); + } + + while (fgets(pwd, MAX_LEN, f)) { + + /* every process tries N-th line, skip others */ + if (line++ % max_id != my_id) + continue; + + len = strlen(pwd); + + /* strip EOL - this is like a input from tty */ + if (len && pwd[len - 1] == '\n') { + pwd[len - 1] = '\0'; + len--; + } + + /* lines starting "#!comment" are comments */ + if (len >= 9 && !strncmp(pwd, "#!comment", 9)) { + /* printf("skipping %s\n", pwd); */ + continue; + } + + /* printf("%d: checking %s\n", my_id, pwd); */ + if (device_type == LUKS) + r = crypt_activate_by_passphrase(cd, NULL, CRYPT_ANY_SLOT, pwd, len, 0); + else if (device_type == TCRYPT) { + struct crypt_params_tcrypt params = { + .flags = CRYPT_TCRYPT_LEGACY_MODES, + .passphrase = pwd, + .passphrase_size = len, + }; + r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); + } + if (r >= 0) { + printf("Found passphrase for slot %d: \"%s\"\n", r, pwd); + break; + } + } + + fclose(f); + crypt_free(cd); + exit(r >= 0 ? 2 : EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + int i, status, procs = 4; + struct crypt_device *cd; + + if (argc < 4 || argc > 5) { + printf("Use: %s luks|tcrypt <device|file> <password file> [#processes] %d\n", argv[0], argc); + exit(EXIT_FAILURE); + } + + if (argc == 5 && (sscanf(argv[4], "%i", &procs) != 1 || procs < 1)) { + printf("Wrong number of processes.\n"); + exit(EXIT_FAILURE); + } + + if (!strcmp(argv[1], "luks")) + device_type = LUKS; + else if (!strcmp(argv[1], "tcrypt")) + device_type = TCRYPT; + else { + printf("Wrong device type %s.\n", argv[1]); + exit(EXIT_FAILURE); + } + + /* crypt_set_debug_level(CRYPT_DEBUG_ALL); */ + + /* + * Need to create temporary keyslot device-mapper devices and allocate loop if needed, + * so root is required here. + */ + if (getuid() != 0) { + printf("You must be root to run this program.\n"); + exit(EXIT_FAILURE); + } + + /* signal all children if anything happens */ + prctl(PR_SET_PDEATHSIG, SIGHUP); + setpriority(PRIO_PROCESS, 0, -5); + + /* we are not going to modify anything, so common init is ok */ + if (crypt_init(&cd, argv[2]) || + (device_type == LUKS && crypt_load(cd, CRYPT_LUKS1, NULL))) { + printf("Cannot open %s.\n", argv[2]); + exit(EXIT_FAILURE); + } + + /* run scan in separate processes, it is up to scheduler to assign CPUs inteligently */ + for (i = 0; i < procs; i++) + check(cd, argv[3], i, procs); + + /* wait until at least one finishes with error or status 2 (key found) */ + while (wait(&status) != -1 && WIFEXITED(status)) { + if (WEXITSTATUS(status) == EXIT_SUCCESS) + continue; + /* kill rest of processes */ + kill(0, SIGHUP); + /* not reached */ + break; + } + exit(0); +} diff --git a/misc/dracut_90reencrypt/README b/misc/dracut_90reencrypt/README new file mode 100644 index 0000000..0672949 --- /dev/null +++ b/misc/dracut_90reencrypt/README @@ -0,0 +1,40 @@ +Example of simple dracut module for reencryption of system +LUKS drive on-the-fly. + +Install in /usr/[share|lib]/dracut/modules.d/90reencrypt, then +build special initramfs "with dracut -a reencrypt -o crypt". +Reencrypt module doesn't work (has a conflict) with crypt module as +of now. After successful reencryption reboot using original initramfs. + +Dracut then recognize argument rd.luks.reencrypt=name:size, +e.g. rd.luks.reencrypt=sda2:52G means only 52G of device +will be reencrypted (default is whole device). +(Name is kernel name of device.) + +If there's more than single active keyslot in the target luks device +you're required to select one keyslot explicitly for reencryption via +rd.luks.reencrypt_keyslot=<keyslot_number> option. Bear in mind that +if you use this option, all other keyslots will get deactivated in the +process. + +Another argument, rd.luks.reencrypt_key=/dev/sda:/path/to/keyfile +can be used to read password for specific keyslot from device containing +filesystem with a keyfile (file with a password). If you omit reencrypt_key +argument, reencryption would work only in case a LUKS container has +exactly one keyslot activated. + +Arguments rd.luks.reencrypt_keyslot and rd.luks.reencrypt_key are not +mandatory. + +Note that reencryption context is stored in ramdisk, any +fail can mean complete lost of data! + +Copyright (C) 2012 Milan Broz <gmazyland@gmail.com> + +This copyrighted material is made available to anyone wishing to use, +modify, copy, or redistribute it subject to the terms and conditions +of the GNU General Public License v.2. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/misc/dracut_90reencrypt/check.old b/misc/dracut_90reencrypt/check.old new file mode 100755 index 0000000..53010b3 --- /dev/null +++ b/misc/dracut_90reencrypt/check.old @@ -0,0 +1,5 @@ +#!/bin/bash + +which cryptsetup-reencrypt >/dev/null 2>&1 || exit 1 + +exit 0 diff --git a/misc/dracut_90reencrypt/install.old b/misc/dracut_90reencrypt/install.old new file mode 100755 index 0000000..6e0523b --- /dev/null +++ b/misc/dracut_90reencrypt/install.old @@ -0,0 +1,6 @@ +#!/bin/bash + +inst cryptsetup-reencrypt + +inst_hook cmdline 30 "$moddir/parse-reencrypt.sh" +inst "$moddir"/reencrypt.sh /sbin/reencrypt diff --git a/misc/dracut_90reencrypt/module-setup.sh b/misc/dracut_90reencrypt/module-setup.sh new file mode 100755 index 0000000..fcd7c92 --- /dev/null +++ b/misc/dracut_90reencrypt/module-setup.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +check() { + [ -x /sbin/cryptsetup-reencrypt ] || return 1 + return 255 +} + +depends() { + echo dm rootfs-block +} + +installkernel() { + # requires hostonly='' override so that loop module is pulled in initramfs + # even if not loaded in actual kernel. dracut bug? + hostonly='' instmods dm_crypt =crypto loop +} + +install() { + if dracut_module_included crypt; then + derror "'reencrypt' can't be installed together with 'crypt'." + derror "Add '-o crypt' option to install reencrypt module." + return 1 + fi + + dracut_install cryptsetup-reencrypt + + # moddir variable is assigned in dracut general shell lib + # shellcheck disable=SC2154 + inst_hook cmdline 30 "$moddir/parse-reencrypt.sh" + inst_simple "$moddir"/reencrypt.sh /sbin/reencrypt + inst_simple "$moddir"/reencrypt-verbose.sh /sbin/cryptsetup-reencrypt-verbose +} diff --git a/misc/dracut_90reencrypt/parse-reencrypt.sh b/misc/dracut_90reencrypt/parse-reencrypt.sh new file mode 100755 index 0000000..5fec191 --- /dev/null +++ b/misc/dracut_90reencrypt/parse-reencrypt.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +REENC=$(getargs rd.luks.reencrypt=) +# shellcheck disable=SC2086 +REENC_DEV=$(echo $REENC | sed 's/:.*//') +# shellcheck disable=SC2086 +REENC_SIZE=$(echo $REENC | sed -n 's/.*://p') + +REENC_KEY=$(getargs rd.luks.reencrypt_key=) +if [ -z "$REENC_KEY" ] ; then + REENC_KEY=none +fi + +REENC_SLOT=$(getargs rd.luks.reencrypt_keyslot=) +if [ -z "$REENC_SLOT" ] ; then + REENC_SLOT=any +fi + +# shellcheck disable=SC2086 +# shellcheck disable=SC1004 +# shellcheck disable=SC2016 +if [ -n "$REENC_DEV" ] ; then +{ + printf 'SUBSYSTEM!="block", GOTO="reenc_end"\n' + printf 'ACTION!="add|change", GOTO="reenc_end"\n' + printf 'KERNEL=="%s", ' $REENC_DEV + printf 'ENV{ID_FS_TYPE}=="crypto_LUKS", RUN+="/sbin/initqueue \ + --unique --onetime --settled --name crypt-reencrypt-%%k \ + /sbin/reencrypt $env{DEVNAME} %s"\n' "$REENC_KEY $REENC_SLOT $REENC_SIZE" + + printf 'ENV{ID_FS_UUID}=="*%s*", ' $REENC_DEV + printf 'ENV{ID_FS_TYPE}=="crypto_LUKS", RUN+="/sbin/initqueue \ + --unique --onetime --settled --name crypt-reencrypt-%%k \ + /sbin/reencrypt $env{DEVNAME} %s"\n' "$REENC_KEY $REENC_SLOT $REENC_SIZE" + printf 'LABEL="reenc_end"\n' +} > /etc/udev/rules.d/69-reencryption.rules + initqueue --unique --finished --name crypt-reencrypt-finished-${REENC_DEV} [ -e /tmp/reencrypted ] +fi diff --git a/misc/dracut_90reencrypt/reencrypt-verbose.sh b/misc/dracut_90reencrypt/reencrypt-verbose.sh new file mode 100755 index 0000000..109ce6e --- /dev/null +++ b/misc/dracut_90reencrypt/reencrypt-verbose.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +# Route stdout to stderr in initrd. Otherwise output is invisible +# unless we run in debug mode. +# shellcheck disable=SC2068 +/sbin/cryptsetup-reencrypt $@ 1>&2 diff --git a/misc/dracut_90reencrypt/reencrypt.sh b/misc/dracut_90reencrypt/reencrypt.sh new file mode 100755 index 0000000..db09e64 --- /dev/null +++ b/misc/dracut_90reencrypt/reencrypt.sh @@ -0,0 +1,84 @@ +#!/bin/sh +# +# $1=$device [$2=keyfile|none [$3=keyslot|any [$4=size]]] +# + +[ -d /sys/module/dm_crypt ] || modprobe dm_crypt + +[ -d /sys/module/loop ] || modprobe loop + +[ -f /tmp/reencrypted ] && exit 0 + +. /lib/dracut-lib.sh + +# if device name is /dev/dm-X, convert to /dev/mapper/name +if [ "${1##/dev/dm-}" != "$1" ]; then + device="/dev/mapper/$(dmsetup info -c --noheadings -o name "$1")" +else + device="$1" +fi + +PARAMS="$device -T 1 --use-fsync --progress-frequency 5 -B 32" +if [ "$3" != "any" ]; then + PARAMS="$PARAMS -S $3" +fi + +if [ -n "$4" ]; then + PARAMS="$PARAMS --device-size $4" +fi + +reenc_readkey() { + keypath="${1#*:}" + keydev="${1%%:*}" + + mntp="/tmp/reencrypted-mount-tmp" + mkdir "$mntp" + mount -r "$keydev" "$mntp" && cat "$mntp/$keypath" + umount "$mntp" + rm -r "$mntp" +} + +# shellcheck disable=SC2086 +# shellcheck disable=SC2164 +reenc_run() { + cwd=$(pwd) + _prompt="LUKS password for REENCRYPTING $device" + cd /tmp + udevadm settle + if [ "$1" = "none" ] ; then + if [ "$2" != "any" ]; then + _prompt="$_prompt, using keyslot $2" + fi + /bin/plymouth ask-for-password \ + --prompt "$_prompt" \ + --command="/sbin/cryptsetup-reencrypt-verbose $PARAMS" + else + info "REENCRYPT using key $1" + reenc_readkey "$1" | /sbin/cryptsetup-reencrypt-verbose -d - $PARAMS + fi + _ret=$? + cd $cwd +} + +info "REENCRYPT $device requested" +# flock against other interactive activities +# shellcheck disable=SC2086 +{ flock -s 9; + reenc_run $2 $3 +} 9>/.console_lock + +if [ $_ret -eq 0 ]; then + # do not ask again + # shellcheck disable=SC2188 + >> /tmp/reencrypted + warn "Reencryption of device $device has finished successfully. Use previous" + warn "initramfs image (without reencrypt module) to boot the system. When" + warn "you leave the emergency shell, the system will reboot." + + emergency_shell -n "(reboot)" + [ -x /usr/bin/systemctl ] && /usr/bin/systemctl reboot + [ -x /sbin/shutdown ] && /sbin/shutdown -r now +fi + +# panic the kernel otherwise +exit 1 diff --git a/misc/keyslot_checker/Makefile b/misc/keyslot_checker/Makefile new file mode 100644 index 0000000..3b159fd --- /dev/null +++ b/misc/keyslot_checker/Makefile @@ -0,0 +1,14 @@ +TARGETS=chk_luks_keyslots +CFLAGS=-O0 -g -Wall -D_GNU_SOURCE +LDLIBS=-lcryptsetup -lm +CC=gcc + +all: $(TARGETS) + +chk_luks_keyslots: chk_luks_keyslots.o + $(CC) -o $@ $^ $(LDLIBS) + +clean: + rm -f *.o *~ core $(TARGETS) + +.PHONY: clean diff --git a/misc/keyslot_checker/README b/misc/keyslot_checker/README new file mode 100644 index 0000000..cd5bf81 --- /dev/null +++ b/misc/keyslot_checker/README @@ -0,0 +1,120 @@ +Purpose +======= + +chk_luks_keyslots is a tool that searches the keyslot area of a +LUKS container for positions where entropy is low and hence +there is a high probability of damage from overwrites of parts +of the key-slot with data such as a RAID superblock or a partition +table. + + +Installation +============ + +1. Install the version of cryptsetup the tool came with. +2. Compile with "make" + +Manual compile can be done with + gcc -lm -lcryptsetup chk_luks_keyslots.c -o chk_luks_keyslots + +Usage +===== + +Call chk_luks_keyslots without arguments for an option summary. + + +Example of a good keyslot area with keys 0 and 2 in use: +-------------------------------------------------------- + +root> ./chk_luks_keyslots /dev/loop0 + +parameters (commandline and LUKS header): + sector size: 512 + threshold: 0.900000 + +- processing keyslot 0: start: 0x001000 end: 0x020400 +- processing keyslot 1: keyslot not in use +- processing keyslot 2: start: 0x041000 end: 0x060400 +- processing keyslot 3: keyslot not in use +- processing keyslot 4: keyslot not in use +- processing keyslot 5: keyslot not in use +- processing keyslot 6: keyslot not in use +- processing keyslot 7: keyslot not in use + + +Same example of a fault in slot 2 at offset 0x50000: +---------------------------------------------------- + +root>./chk_luks_keyslots /dev/loop2 + +parameters (commandline and LUKS header): + sector size: 512 + threshold: 0.900000 + +- processing keyslot 0: start: 0x001000 end: 0x020400 +- processing keyslot 1: keyslot not in use +- processing keyslot 2: start: 0x041000 end: 0x060400 + low entropy at: 0x050000 entropy: 0.549165 +- processing keyslot 3: keyslot not in use +- processing keyslot 4: keyslot not in use +- processing keyslot 5: keyslot not in use +- processing keyslot 6: keyslot not in use +- processing keyslot 7: keyslot not in use + + +Same as last, but verbose: +-------------------------- +root>./chk_luks_keyslots -v /dev/loop2 + +parameters (commandline and LUKS header): + sector size: 512 + threshold: 0.900000 + +- processing keyslot 0: start: 0x001000 end: 0x020400 +- processing keyslot 1: keyslot not in use +- processing keyslot 2: start: 0x041000 end: 0x060400 + low entropy at: 0x050000 entropy: 0.549165 + Binary dump: + 0x050000 54 68 69 73 20 69 73 20 61 20 74 65 73 74 2D 73 This is a test-s + 0x050010 65 63 74 6F 72 20 66 6F 72 20 63 68 6B 5F 6C 75 ector for chk_lu + 0x050020 6B 73 5F 6B 65 79 73 6C 6F 74 73 20 74 68 65 20 ks_keyslots the + 0x050030 71 75 69 63 6B 20 62 72 6F 77 6E 20 66 6F 78 20 quick brown fox + 0x050040 6A 75 6D 70 73 20 6F 76 65 72 20 74 68 65 20 6C jumps over the l + 0x050050 61 7A 79 20 64 6F 67 20 74 68 65 20 71 75 69 63 azy dog the quic + 0x050060 6B 20 62 72 6F 77 6E 20 66 6F 78 20 6A 75 6D 70 k brown fox jump + 0x050070 73 20 6F 76 65 72 20 74 68 65 20 6C 61 7A 79 20 s over the lazy + 0x050080 64 6F 67 20 74 68 65 20 71 75 69 63 6B 20 62 72 dog the quick br + 0x050090 6F 77 6E 20 66 6F 78 20 6A 75 6D 70 73 20 6F 76 own fox jumps ov + 0x0500a0 65 72 20 74 68 65 20 6C 61 7A 79 20 64 6F 67 20 er the lazy dog + 0x0500b0 74 68 65 20 71 75 69 63 6B 20 62 72 6F 77 6E 20 the quick brown + 0x0500c0 66 6F 78 20 6A 75 6D 70 73 20 6F 76 65 72 20 74 fox jumps over t + 0x0500d0 68 65 20 6C 61 7A 79 20 64 6F 67 20 74 68 65 20 he lazy dog the + 0x0500e0 71 75 69 63 6B 20 62 72 6F 77 6E 20 66 6F 78 20 quick brown fox + 0x0500f0 6A 75 6D 70 73 20 6F 76 65 72 20 74 68 65 20 6C jumps over the l + 0x050100 61 7A 79 20 64 6F 67 20 74 68 65 20 71 75 69 63 azy dog the quic + 0x050110 6B 20 62 72 6F 77 6E 20 66 6F 78 20 6A 75 6D 70 k brown fox jump + 0x050120 73 20 6F 76 65 72 20 74 68 65 20 6C 61 7A 79 20 s over the lazy + 0x050130 64 6F 67 20 74 68 65 20 71 75 69 63 6B 20 62 72 dog the quick br + 0x050140 6F 77 6E 20 66 6F 78 20 6A 75 6D 70 73 20 6F 76 own fox jumps ov + 0x050150 65 72 20 74 68 65 20 6C 61 7A 79 20 64 6F 67 20 er the lazy dog + 0x050160 74 68 65 20 71 75 69 63 6B 20 62 72 6F 77 6E 20 the quick brown + 0x050170 66 6F 78 20 6A 75 6D 70 73 20 6F 76 65 72 20 74 fox jumps over t + 0x050180 68 65 20 6C 61 7A 79 20 64 6F 67 20 74 68 65 20 he lazy dog the + 0x050190 71 75 69 63 6B 20 62 72 6F 77 6E 20 66 6F 78 20 quick brown fox + 0x0501a0 6A 75 6D 70 73 20 6F 76 65 72 20 74 68 65 20 6C jumps over the l + 0x0501b0 61 7A 79 20 64 6F 67 20 74 68 65 20 71 75 69 63 azy dog the quic + 0x0501c0 6B 20 62 72 6F 77 6E 20 66 6F 78 20 6A 75 6D 70 k brown fox jump + 0x0501d0 73 20 6F 76 65 72 20 74 68 65 20 6C 61 7A 79 20 s over the lazy + 0x0501e0 64 6F 67 20 74 68 65 20 71 75 69 63 6B 20 62 72 dog the quick br + 0x0501f0 6F 77 6E 20 66 6F 78 20 6A 75 6D 70 73 20 6F 76 own fox jumps ov + +- processing keyslot 3: keyslot not in use +- processing keyslot 4: keyslot not in use +- processing keyslot 5: keyslot not in use +- processing keyslot 6: keyslot not in use +- processing keyslot 7: keyslot not in use + +---- +Copyright (C) 2012, Arno Wagner <arno@wagner.name> +This file is free documentation; the author gives +unlimited permission to copy, distribute and modify it. diff --git a/misc/keyslot_checker/chk_luks_keyslots.c b/misc/keyslot_checker/chk_luks_keyslots.c new file mode 100644 index 0000000..d05aad8 --- /dev/null +++ b/misc/keyslot_checker/chk_luks_keyslots.c @@ -0,0 +1,371 @@ +/* + * LUKS keyslot entropy tester. Works only for header version 1. + * + * Functionality: Determines sample entropy (symbols: bytes) for + * each (by default) 512B sector in each used keyslot. If it + * is lower than a threshold, the sector address is printed + * as it is suspected of having non-"random" data in it, indicating + * damage by overwriting. This can obviously not find overwriting + * with random or random-like data (encrypted, compressed). + * + * Version history: + * v0.1: 09.09.2012 Initial release + * v0.2: 08.10.2012 Converted to use libcryptsetup + * + * Copyright (C) 2012, Arno Wagner <arno@wagner.name> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <ctype.h> +#include <math.h> +#include <fcntl.h> +#include <inttypes.h> +#include <libcryptsetup.h> + +const char *help = +"Version 0.2 [8.10.2012]\n" +"\n" +" chk_luks_keyslots [options] luks-device \n" +"\n" +"This tool checks all keyslots of a LUKS device for \n" +"low entropy sections. If any are found, they are reported. \n" +"This allows to find areas damaged by things like filesystem \n" +"creation or RAID superblocks. \n" +"\n" +"Options: \n" +" -t <num> Entropy threshold. Possible values 0.0 ... 1.0 \n" +" Default: 0.90, which works well for 512B sectors.\n" +" For 512B sectors, you will get frequent misdetections\n" +" at thresholds around 0.94\n" +" Higher value: more sensitive but more false detections.\n" +" -s <num> Sector size. Must divide keyslot-size.\n" +" Default: 512 Bytes.\n" +" Values smaller than 128 are generally not very useful.\n" +" For values smaller than the default, you need to adjust\n" +" the threshold down to reduce misdetection. For values\n" +" larger than the default you need to adjust the threshold\n" +" up to retain sensitivity.\n" +" -v Print found suspicious sectors verbosely. \n" +" -d Print decimal addresses instead of hex ones.\n" +"\n"; + + +/* Config defaults */ + +static int sector_size = 512; +static double threshold = 0.90; +static int print_decimal = 0; +static int verbose = 0; + +/* tools */ + +/* Calculates and returns sample entropy on byte level for + * The argument. + */ +static double ent_samp(unsigned char * buf, int len) +{ + int freq[256]; /* stores symbol frequencies */ + int i; + double e, f; + + /* 0. Plausibility checks */ + if (len <= 0) + return 0.0; + + /* 1. count all frequencies */ + for (i = 0; i < 256; i++) { + freq[i] = 0.0; + } + + for (i = 0; i < len; i ++) + freq[buf[i]]++; + + /* 2. calculate sample entropy */ + e = 0.0; + for (i = 0; i < 256; i++) { + f = freq[i]; + if (f > 0) { + f = f / (double)len; + e += f * log2(f); + } + } + + if (e != 0.0) + e = -1.0 * e; + + e = e / 8.0; + return e; +} + +static void print_address(FILE *out, uint64_t value) +{ + if (print_decimal) { + fprintf(out,"%08" PRIu64 " ", value); + } else { + fprintf(out,"%#08" PRIx64 " ", value); + } +} + +/* uses default "hd" style, i.e. 16 bytes followed by ASCII */ +static void hexdump_line(FILE *out, uint64_t address, unsigned char *buf) { + int i; + static char tbl[16] = "0123456789ABCDEF"; + + fprintf(out," "); + print_address(out, address); + fprintf(out," "); + + /* hex */ + for (i = 0; i < 16; i++) { + fprintf(out, "%c%c", + tbl[(unsigned char)buf[i]>> 4], + tbl[(unsigned char)buf[i] & 0x0f]); + fprintf(out," "); + if (i == 7) + fprintf(out," "); + } + + fprintf(out," "); + + /* ascii */ + for (i = 0; i < 16; i++) { + if (isprint(buf[i])) { + fprintf(out, "%c", buf[i]); + } else { + fprintf(out, "."); + } + } + fprintf(out, "\n"); +} + +static void hexdump_sector(FILE *out, unsigned char *buf, uint64_t address, int len) +{ + int done; + + done = 0; + while (len - done >= 16) { + hexdump_line(out, address + done, buf + done); + done += 16; + } +} + +static int check_keyslots(FILE *out, struct crypt_device *cd, int f_luks) +{ + int i; + double ent; + off_t ofs; + uint64_t start, length, end; + crypt_keyslot_info ki; + unsigned char buffer[sector_size]; + + for (i = 0; i < crypt_keyslot_max(CRYPT_LUKS1) ; i++) { + fprintf(out, "- processing keyslot %d:", i); + ki = crypt_keyslot_status(cd, i); + if (ki == CRYPT_SLOT_INACTIVE) { + fprintf(out, " keyslot not in use\n"); + continue; + } + + if (ki == CRYPT_SLOT_INVALID) { + fprintf(out, "\nError: keyslot invalid.\n"); + return EXIT_FAILURE; + } + + if (crypt_keyslot_area(cd, i, &start, &length) < 0) { + fprintf(stderr,"\nError: querying keyslot area failed for slot %d\n", i); + perror(NULL); + return EXIT_FAILURE; + } + end = start + length; + + fprintf(out, " start: "); + print_address(out, start); + fprintf(out, " end: "); + print_address(out, end); + fprintf(out, "\n"); + + /* check whether sector-size divides size */ + if (length % sector_size != 0) { + fprintf(stderr,"\nError: Argument to -s does not divide keyslot size\n"); + return EXIT_FAILURE; + } + + for (ofs = start; (uint64_t)ofs < end; ofs += sector_size) { + if (lseek(f_luks, ofs, SEEK_SET) != ofs) { + fprintf(stderr,"\nCannot seek to keyslot area.\n"); + return EXIT_FAILURE; + } + if (read(f_luks, buffer, sector_size) != sector_size) { + fprintf(stderr,"\nCannot read keyslot area.\n"); + return EXIT_FAILURE; + } + ent = ent_samp(buffer, sector_size); + if (ent < threshold) { + fprintf(out, " low entropy at: "); + print_address(out, ofs); + fprintf(out, " entropy: %f\n", ent); + if (verbose) { + fprintf(out, " Binary dump:\n"); + hexdump_sector(out, buffer, (uint64_t)ofs, sector_size); + fprintf(out,"\n"); + } + } + } + } + + return EXIT_SUCCESS; +} + +/* Main */ +int main(int argc, char **argv) +{ + /* for option processing */ + int c, r; + char *device; + + /* for use of libcryptsetup */ + struct crypt_device *cd; + + /* Other vars */ + int f_luks; /* device file for the luks device */ + FILE *out; + + /* temporary helper vars */ + int res; + + /* getopt values */ + char *s, *end; + double tvalue; + int svalue; + + /* global initializations */ + out = stdout; + + /* get commandline parameters */ + while ((c = getopt (argc, argv, "t:s:vd")) != -1) { + switch (c) { + case 't': + s = optarg; + tvalue = strtod(s, &end); + if (s == end) { + fprintf(stderr, "\nError: Parsing of argument to -t failed.\n"); + exit(EXIT_FAILURE); + } + + if (tvalue < 0.0 || tvalue > 1.0) { + fprintf(stderr,"\nError: Argument to -t must be in 0.0 ... 1.0\n"); + exit(EXIT_FAILURE); + } + threshold = tvalue; + break; + case 's': + s = optarg; + svalue = strtol(s, &end, 10); + if (s == end) { + fprintf(stderr, "\nError: Parsing of argument to -s failed.\n"); + exit(EXIT_FAILURE); + } + + if (svalue < 1) { + fprintf(stderr,"\nError: Argument to -s must be >= 1 \n"); + exit(EXIT_FAILURE); + } + sector_size = svalue; + break; + case 'v': + verbose = 1; + break; + case 'd': + print_decimal = 1; + break; + case '?': + if (optopt == 't' || optopt == 's') + fprintf (stderr,"\nError: Option -%c requires an argument.\n", + optopt); + else if (isprint (optopt)) { + fprintf(stderr,"\nError: Unknown option `-%c'.\n", optopt); + fprintf(stderr,"\n\n%s", help); + } else { + fprintf (stderr, "\nError: Unknown option character `\\x%x'.\n", + optopt); + fprintf(stderr,"\n\n%s", help); + } + exit(EXIT_SUCCESS); + default: + exit(EXIT_FAILURE); + } + } + + /* parse non-option stuff. Should be exactly one, the device. */ + if (optind+1 != argc) { + fprintf(stderr,"\nError: exactly one non-option argument expected!\n"); + fprintf(stderr,"\n\n%s", help); + exit(EXIT_FAILURE); + } + device = argv[optind]; + + /* test whether we can open and read device */ + /* This is needed as we are reading the actual data + * in the keyslots directly from the LUKS container. + */ + f_luks = open(device, O_RDONLY); + if (f_luks == -1) { + fprintf(stderr,"\nError: Opening of device %s failed:\n", device); + perror(NULL); + exit(EXIT_FAILURE); + } + + /* now get the parameters we need via libcryptsetup */ + /* Basically we need all active keyslots and their placement on disk */ + + /* first init. This does the following: + * - gets us a crypt_device struct with some values filled in + * Note: This does some init stuff we do not need, but that + * should not cause trouble. + */ + + res = crypt_init(&cd, device); + if (res < 0) { + fprintf(stderr, "crypt_init() failed. Maybe not running as root?\n"); + close(f_luks); + exit(EXIT_FAILURE); + } + + /* now load LUKS header into the crypt_device + * This should also make sure a valid LUKS1 header is on disk + * and hence we should be able to skip magic and version checks. + */ + res = crypt_load(cd, CRYPT_LUKS1, NULL); + if (res < 0) { + fprintf(stderr, "crypt_load() failed. LUKS header too broken/absent?\n"); + crypt_free(cd); + close(f_luks); + exit(EXIT_FAILURE); + } + + fprintf(out, "\nparameters (commandline and LUKS header):\n"); + fprintf(out, " sector size: %d\n", sector_size); + fprintf(out, " threshold: %0f\n\n", threshold); + + r = check_keyslots(out, cd, f_luks); + + crypt_free(cd); + close(f_luks); + return r; +} diff --git a/misc/luks-header-from-active b/misc/luks-header-from-active new file mode 100755 index 0000000..a94ad33 --- /dev/null +++ b/misc/luks-header-from-active @@ -0,0 +1,59 @@ +#!/bin/bash + +# Try to get LUKS info and master key from active mapping and prepare parameters for cryptsetup. +# +# Copyright (C) 2010,2011,2012 Milan Broz <gmazyland@gmail.com> +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions +# of the GNU General Public License v.2. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +umask 0077 + +fail() { echo -e $1 ; exit 1 ; } +field() { echo $(dmsetup table --target crypt --showkeys $DEVICE | sed 's/.*: //' | cut -d' ' -f$1) ; } +field_uuid() { echo $(dmsetup info $1 --noheadings -c -o uuid) ; } +field_device() { + TEMP=$(readlink /sys/dev/block/$1 | sed -e 's/.*\///') + if [ ${TEMP:0:3} = "dm-" -a -e /sys/block/$TEMP/dm/name ] ; then + TEMP=/dev/mapper/$(cat /sys/block/$TEMP/dm/name) + else + TEMP=/dev/$TEMP + fi + echo $TEMP +} + +which readlink >/dev/null || fail "You need readlink (part of coreutils package)." +which xxd >/dev/null || fail "You need xxd (part of vim package) installed to convert key." + +[ -z "$2" ] && fail "Recover LUKS header from active mapping, use:\n $0 crypt_mapped_device mk_file_name" + +DEVICE=$1 +MK_FILE=$2 + +[ -z "$(field 4)" ] && fail "Mapping $1 not active or it is not crypt target." + +CIPHER=$(field 4) +OFFSET=$(field 8) +SYS_DEVICE=$(field 7) +REAL_DEVICE=$(field_device $SYS_DEVICE) +KEY=$(field 5) +KEY_SIZE=$(( ${#KEY} / 2 * 8 )) +SYS_UUID=$(field_uuid $DEVICE) +UUID="${SYS_UUID:12:8}-${SYS_UUID:20:4}-${SYS_UUID:24:4}-${SYS_UUID:28:4}-${SYS_UUID:32:12}" + +#echo "CIPHER=$CIPHER OFFSET=$OFFSET SYS_DEVICE=$SYS_DEVICE REAL_DEVICE=$REAL_DEVICE KEY_SIZE=$KEY_SIZE KEY=$KEY UUID=$UUID SYS_UUID=$SYS_UUID" + +[ -z "$CIPHER" -o -z "$OFFSET" -o "$OFFSET" -le 383 -o \ +-z "$KEY" -o -z "$UUID" -o -z "$REAL_DEVICE" -o "${SYS_UUID:0:12}" != "CRYPT-LUKS1-" ] && \ +fail "Incompatible device, sorry." + +echo "Generating master key to file $MK_FILE." +echo -E -n $KEY| xxd -r -p >$MK_FILE + +echo "You can now try to reformat LUKS device using:" +echo " cryptsetup luksFormat -c $CIPHER -s $KEY_SIZE --align-payload=$OFFSET --master-key-file=$MK_FILE --uuid=$UUID $REAL_DEVICE" diff --git a/misc/luks2_keyslot_example/Makefile b/misc/luks2_keyslot_example/Makefile new file mode 100644 index 0000000..777eafa --- /dev/null +++ b/misc/luks2_keyslot_example/Makefile @@ -0,0 +1,24 @@ +TARGET=keyslot_test +CFLAGS=-O0 -g -Wall -D_GNU_SOURCE -I ../../lib/ +LDLIBS=-ljson-c -luuid -lgcrypt -ldevmapper -lpthread -lssh +CC=gcc + +TARGET2=keyslot_test_remote_pass + +SOURCES=keyslot_test.c +OBJECTS=$(SOURCES:.c=.o) +SOURCES2=keyslot_test_remote_pass.c +OBJECTS2=$(SOURCES2:.c=.o) + +all: $(TARGET) $(TARGET2) $(TARGET4) + +$(TARGET): $(OBJECTS) ../../.libs/libcryptsetup.a + $(CC) -o $@ $^ $(LDLIBS) + +$(TARGET2): $(OBJECTS2) ../../.libs/libcryptsetup.a + $(CC) -o $@ $^ $(LDLIBS) + +clean: + rm -f *.o *~ core $(TARGET) $(TARGET2) + +.PHONY: clean diff --git a/misc/luks2_keyslot_example/README b/misc/luks2_keyslot_example/README new file mode 100644 index 0000000..ac4ab6b --- /dev/null +++ b/misc/luks2_keyslot_example/README @@ -0,0 +1,3 @@ +to create LUKSv2 container run: + +$(top_level_dir)/src/cryptsetup --type luks2 luksFormat /dev/xxx diff --git a/misc/luks2_keyslot_example/keyslot_test.c b/misc/luks2_keyslot_example/keyslot_test.c new file mode 100644 index 0000000..5b8cc9a --- /dev/null +++ b/misc/luks2_keyslot_example/keyslot_test.c @@ -0,0 +1,409 @@ +/* + * Example of LUKS2 kesylot handler (EXAMPLE) + * + * Copyright (C) 2016-2021 Milan Broz <gmazyland@gmail.com> + * + * Use: + * - generate LUKS device + * - store passphrase used in previous step remotely (single line w/o \r\n) + * - add new token using this example + * - activate device by token + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <json-c/json.h> +#include <libssh/libssh.h> +#include <libssh/sftp.h> +#include "libcryptsetup.h" + +#define TOKEN_NUM 0 + +#define PASSWORD_LENGTH 8192 + +typedef int (*password_cb_func) (char **password); + +static json_object *get_token_jobj(struct crypt_device *cd, int token) +{ + const char *json_slot; + + /* libcryptsetup API call */ + if (crypt_token_json_get(cd, token, &json_slot)) + return NULL; + + return json_tokener_parse(json_slot); +} + +static int download_remote_password(struct crypt_device *cd, ssh_session ssh, + const char *path, char **password, + size_t *password_len) +{ + char *pass; + size_t pass_len; + int r; + sftp_attributes sftp_attr = NULL; + sftp_session sftp = NULL; + sftp_file file = NULL; + + + sftp = sftp_new(ssh); + if (!sftp) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_init(sftp); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot init sftp session: "); + goto out; + } + + file = sftp_open(sftp, path, O_RDONLY, 0); + if (!file) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + sftp_attr = sftp_fstat(file); + if (!sftp_attr) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot stat sftp file: "); + r = SSH_FX_FAILURE; + goto out; + } + + pass_len = sftp_attr->size > PASSWORD_LENGTH ? PASSWORD_LENGTH : sftp_attr->size; + pass = malloc(pass_len); + if (!pass) { + crypt_log(cd, CRYPT_LOG_ERROR, "Not enough memory.\n"); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_read(file, pass, pass_len); + if (r < 0 || (size_t)r != pass_len) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot read remote key: "); + r = SSH_FX_FAILURE; + goto out; + } + + *password = pass; + *password_len = pass_len; + + r = SSH_OK; +out: + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + free(pass); + } + + if (sftp_attr) + sftp_attributes_free(sftp_attr); + + if (file) + sftp_close(file); + if (sftp) + sftp_free(sftp); + return r == SSH_OK ? 0 : -EINVAL; +} + +static ssh_session ssh_session_init(struct crypt_device *cd, + const char *host, + const char *user) +{ + int r, port = 22; + ssh_session ssh = ssh_new(); + if (!ssh) + return NULL; + + ssh_options_set(ssh, SSH_OPTIONS_HOST, host); + ssh_options_set(ssh, SSH_OPTIONS_USER, user); + ssh_options_set(ssh, SSH_OPTIONS_PORT, &port); + + crypt_log(cd, CRYPT_LOG_NORMAL, "Initiating ssh session.\n"); + + r = ssh_connect(ssh); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Connection failed: "); + goto out; + } + + r = ssh_is_server_known(ssh); + if (r != SSH_SERVER_KNOWN_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Server not known: "); + r = SSH_AUTH_ERROR; + goto out; + } + + r = SSH_OK; + + /* initialise list of authentication methods. yes, according to official libssh docs... */ + ssh_userauth_none(ssh, NULL); +out: + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + ssh_disconnect(ssh); + ssh_free(ssh); + ssh = NULL; + } + + return ssh; +} + +static void ssh_session_close(ssh_session ssh) +{ + if (ssh) { + ssh_disconnect(ssh); + ssh_free(ssh); + } +} + +static int _public_key_auth(struct crypt_device *cd, ssh_session ssh) +{ + int r; + ssh_key pkey = NULL; + + crypt_log(cd, CRYPT_LOG_DEBUG, "Trying public key authentication method.\n"); + + if (!(ssh_userauth_list(ssh, NULL) & SSH_AUTH_METHOD_PUBLICKEY)) { + crypt_log(cd, CRYPT_LOG_ERROR, "Public key auth method not allowed on host.\n"); + return SSH_AUTH_ERROR; + } + + r = ssh_pki_import_privkey_file("/home/user/.ssh/id_rsa", NULL, NULL, NULL, &pkey); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Failed to import private key\n"); + + return r; + } + + r = ssh_userauth_try_publickey(ssh, NULL, pkey); + if (r == SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_DEBUG, "Public key method accepted.\n"); + r = ssh_userauth_publickey(ssh, NULL, pkey); + } + + ssh_key_free(pkey); + + if (r != SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_ERROR, "Public key authentication error: "); + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + } + + return r; +} + +static int _password_auth(struct crypt_device *cd, ssh_session ssh, password_cb_func pcb) +{ + int r = SSH_AUTH_ERROR; + char *ssh_password = NULL; + + if (!(ssh_userauth_list(ssh, NULL) & SSH_AUTH_METHOD_PASSWORD)) { + crypt_log(cd, CRYPT_LOG_ERROR, "Password auth method not allowed on host.\n"); + return r; + } + + if (pcb(&ssh_password)) { + crypt_log(cd, CRYPT_LOG_ERROR, "Failed to process password.\n"); + return r; + } + + r = ssh_userauth_password(ssh, NULL, ssh_password); + + free(ssh_password); + + if (r != SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_ERROR, "Password authentication error: "); + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + } + + return r; +} + +static int SSHTEST_token_open(struct crypt_device *cd, + int token, + char **password, + size_t *password_len, + void *usrptr) +{ + int r; + json_object *jobj_server, *jobj_user, *jobj_path, *jobj_token; + ssh_session ssh; + password_cb_func pcb = usrptr; /* custom password callback */ + + jobj_token = get_token_jobj(cd, token); + json_object_object_get_ex(jobj_token, "ssh_server", &jobj_server); + json_object_object_get_ex(jobj_token, "ssh_user", &jobj_user); + json_object_object_get_ex(jobj_token, "ssh_path", &jobj_path); + + ssh = ssh_session_init(cd, json_object_get_string(jobj_server), + json_object_get_string(jobj_user)); + if (!ssh) + return -EINVAL; + + r = _public_key_auth(cd, ssh); + + /* try password method fallback. superficial example use case for an usrptr */ + if (r != SSH_AUTH_SUCCESS && pcb) { + crypt_log(cd, CRYPT_LOG_DEBUG, "Trying password method instead.\n"); + r = _password_auth(cd, ssh, pcb); + } + + if (r == SSH_AUTH_SUCCESS) + r = download_remote_password(cd, ssh, json_object_get_string(jobj_path), + password, password_len); + + ssh_session_close(ssh); + + return r ? -EINVAL : r; +} + +const crypt_token_handler SSHTEST_token = { + .name = "sshkeytest", + .open = SSHTEST_token_open, +}; + +static int token_add(const char *device, const char *server, + const char *user, const char *path) +{ + struct crypt_device *cd = NULL; + json_object *jobj = NULL, *jobj_keyslots; + int r; + + r = crypt_token_register(&SSHTEST_token); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + jobj = json_object_new_object(); + json_object_object_add(jobj, "type", json_object_new_string(SSHTEST_token.name)); /* mandatory */ + + jobj_keyslots = json_object_new_array(); + json_object_array_add(jobj_keyslots, json_object_new_string("0")); /* assign to first keyslot only */ + json_object_object_add(jobj, "keyslots", jobj_keyslots); /* mandatory array field (may be empty and assigned later */ + + /* custom metadata */ + json_object_object_add(jobj, "ssh_server", json_object_new_string(server)); + json_object_object_add(jobj, "ssh_user", json_object_new_string(user)); + json_object_object_add(jobj, "ssh_path", json_object_new_string(path)); + + /* libcryptsetup API call */ + r = crypt_token_json_set(cd, TOKEN_NUM, json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN)); + + crypt_free(cd); + json_object_put(jobj); + + return EXIT_SUCCESS; +} + + +/* naive implementation of password prompt. Yes it will print out the password on input :) */ +static int ssh_password_callback(char **ssh_password) +{ + ssize_t i; + char *pass = malloc(512); + + if (!pass) + return -ENOMEM; + + fprintf(stdout, "Host asks for password:\n"); + + i = read(STDIN_FILENO, pass, 512); + if (i > 0) { + pass[i-1] = '\0'; + i = 0; + } else if (i == 0) { /* EOF */ + *pass = '\0'; + i = -1; + } + + if (!i) + *ssh_password = pass; + else + free(pass); + + return i; +} + +static int open_by_token(const char *device, const char *name) +{ + struct crypt_device *cd = NULL; + int r; + + r = crypt_token_register(&SSHTEST_token); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + r = crypt_activate_by_token(cd, name, TOKEN_NUM, ssh_password_callback, 0); + + crypt_free(cd); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static void keyslot_help(void) +{ + printf("Use parameters:\n add device server user path\n" + " open device name\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + crypt_set_debug_level(CRYPT_LOG_DEBUG); + + /* Adding slot to device */ + if (argc == 6 && !strcmp("add", argv[1])) + return token_add(argv[2], argv[3], argv[4], argv[5]); + + /* Key check without activation */ + if (argc == 3 && !strcmp("open", argv[1])) + return open_by_token(argv[2], NULL); + + /* Key check with activation (requires root) */ + if (argc == 4 && !strcmp("open", argv[1])) + return open_by_token(argv[2], argv[3]); + + keyslot_help(); + return 1; +} diff --git a/misc/luks2_keyslot_example/keyslot_test_remote_pass.c b/misc/luks2_keyslot_example/keyslot_test_remote_pass.c new file mode 100644 index 0000000..5aa9500 --- /dev/null +++ b/misc/luks2_keyslot_example/keyslot_test_remote_pass.c @@ -0,0 +1,264 @@ +/* + * Example of LUKS2 token storing third party metadata (EXAMPLE) + * + * Copyright (C) 2016-2021 Milan Broz <gmazyland@gmail.com> + * + * Use: + * - generate LUKS device + * - store passphrase used in previous step remotely (single line w/o \n\r) + * - add new token using this example + * - activate device with passphrase recovered remotely using the example + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <json-c/json.h> +#include <libssh/libssh.h> +#include <libssh/sftp.h> +#include "libcryptsetup.h" + +#define TOKEN_NUM 0 +#define TOKEN_TYPE "sshkeytest" + +#define PASSWORD_LENGTH 8192 + +static json_object *get_token_jobj(struct crypt_device *cd, int token) +{ + const char *json_slot; + + if (crypt_token_json_get(cd, token, &json_slot)) + return NULL; + + return json_tokener_parse(json_slot); +} + +static int read_remote_passphrase(struct crypt_device *cd, const char *host, + const char *user, const char *path, + char *password, size_t password_size) +{ + ssh_session ssh = NULL; + sftp_session sftp = NULL; + sftp_file file = NULL; + ssh_key pkey = NULL; + + int r, port = 22; + + ssh = ssh_new(); + if (!ssh) + return -EINVAL; + + ssh_options_set(ssh, SSH_OPTIONS_HOST, host); + ssh_options_set(ssh, SSH_OPTIONS_USER, user); + ssh_options_set(ssh, SSH_OPTIONS_PORT, &port); + + r = ssh_connect(ssh); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Connection failed: "); + goto out; + } + + r = ssh_is_server_known(ssh); + if (r != SSH_SERVER_KNOWN_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Server not known: "); + r = SSH_AUTH_ERROR; + goto out; + } + + r = ssh_pki_import_privkey_file("/home/user/.ssh/id_rsa", NULL, NULL, NULL, &pkey); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "error\n"); + r = SSH_AUTH_ERROR; + goto out; + } + + r = ssh_userauth_publickey(ssh, user, pkey); + /* or r = ssh_userauth_publickey_auto(ssh, user, NULL); */ + if (r != SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_ERROR, "Public key authentication error: "); + goto out; + } + + sftp = sftp_new(ssh); + if (!sftp) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_init(sftp); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot init sftp session: "); + goto out; + } + + file = sftp_open(sftp, path, O_RDONLY, 0); + if (!file) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_read(file, password, password_size); + if (r < 1 || (size_t)r >= password_size) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot read remote password: "); + r = SSH_FX_FAILURE; + goto out; + } + + r = SSH_OK; +out: + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + } + + if (pkey) + ssh_key_free(pkey); + + if (file) + sftp_close(file); + if (sftp) + sftp_free(sftp); + ssh_disconnect(ssh); + ssh_free(ssh); + return r == SSH_OK ? 0 : -EINVAL; +} + +static int token_add(const char *device, const char *server, + const char *user, const char *path) +{ + struct crypt_device *cd = NULL; + json_object *jobj = NULL, *jobj_keyslots; + int r; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + jobj = json_object_new_object(); + + /* 'type' is mandatory field */ + json_object_object_add(jobj, "type", json_object_new_string(TOKEN_TYPE)); + + /* 'keyslots' is mandatory field (may be empty) */ + jobj_keyslots = json_object_new_array(); + json_object_array_add(jobj_keyslots, json_object_new_string("0")); + json_object_array_add(jobj_keyslots, json_object_new_string("1")); + json_object_object_add(jobj, "keyslots", jobj_keyslots); + + /* third party values */ + json_object_object_add(jobj, "ssh_server", json_object_new_string(server)); + json_object_object_add(jobj, "ssh_user", json_object_new_string(user)); + json_object_object_add(jobj, "ssh_path", json_object_new_string(path)); + + r = crypt_token_json_set(cd, TOKEN_NUM, json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN)); + + crypt_free(cd); + json_object_put(jobj); + + return EXIT_SUCCESS; +} + +static int download_remote_password(struct crypt_device *cd, char *password, size_t password_len) +{ + json_object *jobj_server, *jobj_user, *jobj_path, *jobj_keyslot; + + /* get token json object representation as string */ + jobj_keyslot = get_token_jobj(cd, TOKEN_NUM); + if (!jobj_keyslot) + return -EINVAL; + + + /* extract third party metadata necessary to extract passphrase remotely */ + json_object_object_get_ex(jobj_keyslot, "ssh_server", &jobj_server); + json_object_object_get_ex(jobj_keyslot, "ssh_user", &jobj_user); + json_object_object_get_ex(jobj_keyslot, "ssh_path", &jobj_path); + + return read_remote_passphrase(cd, json_object_get_string(jobj_server), + json_object_get_string(jobj_user), + json_object_get_string(jobj_path), + password, password_len); +} + +static int open_by_remote_password(const char *device, const char *name) +{ + char password[PASSWORD_LENGTH+1]; + struct crypt_device *cd = NULL; + int r; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + /* custom routines to acquire password */ + r = download_remote_password(cd, password, sizeof(password)); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + password[PASSWORD_LENGTH] = '\0'; + + /* open first genuine LUKS2 keyslot available provided the password matches */ + /* for the sake of simplicity password is a string */ + r = crypt_activate_by_passphrase(cd, name, CRYPT_ANY_SLOT, password, strlen(password), 0); + + crypt_free(cd); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static void keyslot_help(void) +{ + printf("Use parameters:\n add device server user path\n" + " open device name\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + crypt_set_debug_level(CRYPT_LOG_DEBUG); + + /* Adding slot to device */ + if (argc == 6 && !strcmp("add", argv[1])) + return token_add(argv[2], argv[3], argv[4], argv[5]); + + /* Password check without activation */ + if (argc == 3 && !strcmp("open", argv[1])) + return open_by_remote_password(argv[2], NULL); + + /* Password check with activation (requires root) */ + if (argc == 4 && !strcmp("open", argv[1])) + return open_by_remote_password(argv[2], argv[3]); + + keyslot_help(); + return 1; +} |