summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile12
-rw-r--r--README-mkinitcpio.md16
-rw-r--r--README.md3
-rw-r--r--bin/Makefile6
-rw-r--r--bin/ykfde.c230
-rw-r--r--config.def.h2
-rw-r--r--mkinitcpio/ykfde-2f14
-rw-r--r--systemd/ykfde-2f20
-rw-r--r--systemd/ykfde-2f.service16
-rw-r--r--udev/Makefile2
-rw-r--r--udev/ykfde.c234
12 files changed, 396 insertions, 160 deletions
diff --git a/.gitignore b/.gitignore
index 7a94410..5bcbdc8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@ config.h
bin/ykfde
bin/ykfde-cpio
udev/ykfde
+version.h
*.html
diff --git a/Makefile b/Makefile
index 77065ac..a7b2c46 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@ VERSION := 0.5.2
all: bin/ykfde bin/ykfde-cpio udev/ykfde README.html README-mkinitcpio.html README-dracut.html
-bin/ykfde: bin/ykfde.c config.h
+bin/ykfde: bin/ykfde.c config.h version.h
$(MAKE) -C bin ykfde
bin/ykfde-cpio: bin/ykfde-cpio.c config.h
@@ -24,6 +24,11 @@ udev/ykfde: udev/ykfde.c config.h
config.h: config.def.h
$(CP) config.def.h config.h
+version.h: $(wildcard .git/HEAD .git/index .git/refs/tags/*) Makefile
+ echo "#ifndef VERSION" > $@
+ echo "#define VERSION \"$(shell git describe --tags --long 2>/dev/null || echo ${VERSION})\"" >> $@
+ echo "#endif" >> $@
+
%.html: %.md
$(MD) $< > $@
$(SED) -i 's/\(README[-[:alnum:]]*\).md/\1.html/g' $@
@@ -35,6 +40,8 @@ install-bin: bin/ykfde udev/ykfde
$(MAKE) -C udev install
$(INSTALL) -D -m0644 conf/ykfde.conf $(DESTDIR)/etc/ykfde.conf
$(INSTALL) -D -m0644 systemd/ykfde-cpio.service $(DESTDIR)/usr/lib/systemd/system/ykfde-cpio.service
+ $(INSTALL) -D -m0644 systemd/ykfde-2f.service $(DESTDIR)/usr/lib/systemd/system/ykfde-2f.service
+ $(INSTALL) -D -m0755 systemd/ykfde-2f $(DESTDIR)/usr/lib/systemd/scripts/ykfde-2f
$(INSTALL) -d -m0700 $(DESTDIR)/etc/ykfde.d/
install-doc: README.html README-mkinitcpio.html README-dracut.html
@@ -48,6 +55,7 @@ install-doc: README.html README-mkinitcpio.html README-dracut.html
install-mkinitcpio: install-bin install-doc
$(INSTALL) -D -m0644 mkinitcpio/ykfde $(DESTDIR)/usr/lib/initcpio/install/ykfde
$(INSTALL) -D -m0644 mkinitcpio/ykfde-cpio $(DESTDIR)/usr/lib/initcpio/install/ykfde-cpio
+ $(INSTALL) -D -m0644 mkinitcpio/ykfde-2f $(DESTDIR)/usr/lib/initcpio/install/ykfde-2f
$(INSTALL) -D -m0644 udev/20-ykfde.rules $(DESTDIR)/usr/lib/initcpio/udev/20-ykfde.rules
install-dracut: install-bin install-doc
@@ -59,7 +67,7 @@ install-dracut: install-bin install-doc
clean:
$(MAKE) -C bin clean
$(MAKE) -C udev clean
- $(RM) -f README.html README-mkinitcpio.html README-dracut.html
+ $(RM) -f README.html README-mkinitcpio.html README-dracut.html version.h
distclean: clean
$(RM) -f config.h
diff --git a/README-mkinitcpio.md b/README-mkinitcpio.md
index 8f25819..8043277 100644
--- a/README-mkinitcpio.md
+++ b/README-mkinitcpio.md
@@ -13,6 +13,7 @@ To compile and use yubikey full disk encryption you need:
* [iniparser](http://ndevilla.free.fr/iniparser/)
* [systemd](http://www.freedesktop.org/wiki/Software/systemd/)
* [cryptsetup](http://code.google.com/p/cryptsetup/)
+* keyutils and linux with `CONFIG_KEYS`
* [mkinitcpio](https://projects.archlinux.org/mkinitcpio.git/)
* [markdown](http://daringfireball.net/projects/markdown/) (HTML documentation)
* [libarchive](http://www.libarchive.org/) (Update challenge on boot)
@@ -89,4 +90,19 @@ Additionally enable `systemd` service `ykfde-cpio.service` and make your
bootloader load the new `cpio` image `/boot/ykfde-challenges.img` (in
addition to your usual initramfs).
+### Optional `ykfde-2f` hook for second factor
+
+This gives the option to add a second factor for authentication.
+With this you need your Yubikey and an additional passphrase to boot
+your systemd.
+
+Add a second factor with `ykfde`:
+
+> ykfde -s xyz
+
+Add `ykfde-2f` to your hook list in `/etc/mkinitcpio.conf` and rebuild
+your initramfs with:
+
+> mkinitcpio -p linux
+
Reboot and have fun!
diff --git a/README.md b/README.md
index a27d499..61e6e1d 100644
--- a/README.md
+++ b/README.md
@@ -18,9 +18,6 @@ distributions. Please look at what matches best for you.
Limitation / TODO
-----------------
-* [systemd password agents](http://www.freedesktop.org/wiki/Software/systemd/PasswordAgents/)
- do not support nested queries. That is why we can not ask for a
- password ourselfs, breaking two factor authentication (2FA).
* When using your additional initramfs `grub-mkconfig` does not know
about that. Regenerating `grub` configuration file `grub.cfg` will
overwrite our changes.
diff --git a/bin/Makefile b/bin/Makefile
index 6e77de8..25faea9 100644
--- a/bin/Makefile
+++ b/bin/Makefile
@@ -3,12 +3,12 @@ CC := gcc
INSTALL := install
RM := rm
# flags
-CFLAGS += -std=c11 -O2 -fpic -pie -Wall -Werror
+CFLAGS += -std=gnu11 -O2 -fpic -pie -Wall -Werror
all: ykfde ykfde-cpio
-ykfde: ykfde.c ../config.h
- $(CC) $(CFLAGS) -lykpers-1 -lyubikey -liniparser -lcryptsetup $(LDFLAGS) -o ykfde ykfde.c
+ykfde: ykfde.c ../config.h ../version.h
+ $(CC) $(CFLAGS) -lcryptsetup -liniparser -lkeyutils -lykpers-1 -lyubikey $(LDFLAGS) -o ykfde ykfde.c
ykfde-cpio: ykfde-cpio.c ../config.h
$(CC) $(CFLAGS) -larchive $(LDFLAGS) -o ykfde-cpio ykfde-cpio.c
diff --git a/bin/ykfde.c b/bin/ykfde.c
index 075a7a2..cace8b2 100644
--- a/bin/ykfde.c
+++ b/bin/ykfde.c
@@ -5,17 +5,11 @@
* of the GNU General Public License, incorporated herein by reference.
*
* compile with:
- * $ gcc -o ykfde ykfde.c -lykpers-1 -lyubikey -lcryptsetup -liniparser
+ * $ gcc -o ykfde ykfde.c -lcryptsetup -liniparser -lkeyutils -lykpers-1 -lyubikey
*/
-#ifndef _XOPEN_SOURCE
-# define _XOPEN_SOURCE
-# ifndef _XOPEN_SOURCE_EXTENDED
-# define _XOPEN_SOURCE_EXTENDED
-# endif
-#endif
-
#include <fcntl.h>
+#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -24,6 +18,8 @@
#include <iniparser.h>
+#include <keyutils.h>
+
#include <yubikey.h>
#include <ykpers-1/ykdef.h>
#include <ykpers-1/ykcore.h>
@@ -31,18 +27,40 @@
#include <libcryptsetup.h>
#include "../config.h"
+#include "../version.h"
-/* challenge is 64 byte,
- * HMAC-SHA1 response is 40 byte */
-#define CHALLENGELEN 64
+#define PROGNAME "ykfde"
+
+/* Yubikey supports write of 64 byte challenge to slot, returns
+ * HMAC-SHA1 response.
+ *
+ * Lengths are defined in ykpers-1/ykdef.h:
+ * SHA1_MAX_BLOCK_SIZE 64
+ * SHA1_DIGEST_SIZE 20
+ *
+ * For passphrase we use hex encoded digest, that is twice the
+ * length of binary digest. */
+#define CHALLENGELEN SHA1_MAX_BLOCK_SIZE
#define RESPONSELEN SHA1_MAX_BLOCK_SIZE
#define PASSPHRASELEN SHA1_DIGEST_SIZE * 2
+const static char optstring[] = "hn:s:V";
+const static struct option options_long[] = {
+ /* name has_arg flag val */
+ { "help", no_argument, NULL, 'h' },
+ { "2nd-factor", required_argument, NULL, 's' },
+ { "new-2nd-factor", required_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'V' },
+ { 0, 0, 0, 0 }
+};
+
int main(int argc, char **argv) {
+ unsigned int version = 0, help = 0;
char challenge_old[CHALLENGELEN + 1],
+ challenge_old_no_2f[CHALLENGELEN + 1],
challenge_new[CHALLENGELEN + 1],
- repose_old[RESPONSELEN],
- repose_new[RESPONSELEN],
+ response_old[RESPONSELEN],
+ response_new[RESPONSELEN],
passphrase_old[PASSPHRASELEN + 1],
passphrase_new[PASSPHRASELEN + 1];
char challengefilename[sizeof(CHALLENGEDIR) + 11 /* "/challenge-" */ + 10 /* unsigned int in char */ + 1],
@@ -54,9 +72,15 @@ int main(int argc, char **argv) {
/* cryptsetup */
const char * device_name;
int8_t luks_slot = -1;
- struct crypt_device *cd;
- crypt_status_info cs;
- crypt_keyslot_info ck;
+ struct crypt_device *cryptdevice;
+ crypt_status_info cryptstatus;
+ crypt_keyslot_info cryptkeyslot;
+ /* keyutils */
+ int second_factor = 1;
+ key_serial_t key;
+ void * payload = NULL;
+ char * new_2nd_factor = NULL;
+ size_t plen = 0;
/* yubikey */
YK_KEY * yk;
uint8_t yk_slot = SLOT_CHAL_HMAC2;
@@ -64,16 +88,47 @@ int main(int argc, char **argv) {
/* iniparser */
dictionary * ini;
char section_ykslot[10 /* unsigned int in char */ + 1 + sizeof(CONFYKSLOT) + 1];
- char section_luksslot[10 /* unsigned int in char */ + 1 + sizeof(CONFLUKSSLOT) + 1];
+ char section_luksslot[10 + 1 + sizeof(CONFLUKSSLOT) + 1];
+
+ /* get command line options */
+ while ((i = getopt_long(argc, argv, optstring, options_long, NULL)) != -1)
+ switch (i) {
+ case 'h':
+ help++;
+ break;
+ case 'n':
+ new_2nd_factor = strdup(optarg);
+ memset(optarg, '*', strlen(optarg));
+ break;
+ case 's':
+ payload = strdup(optarg);
+ memset(optarg, '*', strlen(optarg));
+ break;
+ case 'V':
+ version++;
+ break;
+ }
+
+ if (version > 0)
+ printf("%s: %s v%s (compiled: " __DATE__ ", " __TIME__ ")\n", argv[0], PROGNAME, VERSION);
+
+ if (help > 0)
+ fprintf(stderr, "usage: %s [-h] [-n <new-2nd-factor>] [-s <2nd-factor>] [-V]\n", argv[0]);
+
+ if (version > 0 || help > 0)
+ return EXIT_SUCCESS;
+
/* initialize random seed */
gettimeofday(&tv, NULL);
srand(tv.tv_usec * tv.tv_sec);
+ /* initialize static buffers */
memset(challenge_old, 0, CHALLENGELEN + 1);
+ memset(challenge_old_no_2f, 0, CHALLENGELEN + 1);
memset(challenge_new, 0, CHALLENGELEN + 1);
- memset(repose_old, 0, RESPONSELEN);
- memset(repose_new, 0, RESPONSELEN);
+ memset(response_old, 0, RESPONSELEN);
+ memset(response_new, 0, RESPONSELEN);
memset(passphrase_old, 0, PASSPHRASELEN + 1);
memset(passphrase_new, 0, PASSPHRASELEN + 1);
@@ -130,30 +185,32 @@ int main(int argc, char **argv) {
luks_slot = iniparser_getint(ini, section_luksslot, luks_slot);
if (luks_slot < 0) {
rc = EXIT_FAILURE;
- fprintf(stderr, "Please set LUKS key slot for Yubikey with serial %d!\n", serial);
- printf("Add something like this to " CONFIGFILE ":\n\n[%d]\nluks slot = 1\n", serial);
+ fprintf(stderr, "Please set LUKS key slot for Yubikey with serial %d!\n"
+ "Add something like this to " CONFIGFILE ":\n\n"
+ "[%d]\nluks slot = 1\n", serial, serial);
goto out40;
}
+ if (payload == NULL) {
+ /* get second factor from key store */
+ if ((key = request_key("user", "ykfde-2f", NULL, 0)) < 0)
+ fprintf(stderr, "Failed requesting key. That's ok if you do not use\n"
+ "second factor. Give it manually if required.\n");
+
+ if (key > -1) {
+ /* if we have a key id we have a key - so this should succeed */
+ if ((rc = keyctl_read_alloc(key, &payload)) < 0) {
+ perror("Failed reading payload from key");
+ goto out40;
+ }
+ } else
+ payload = strdup("");
+ }
+
/* get random number and limit to printable ASCII character (32 to 126) */
for(i = 0; i < CHALLENGELEN; i++)
challenge_new[i] = (rand() % (126 - 32)) + 32;
- /* do challenge/response and encode to hex */
- if ((rc = yk_challenge_response(yk, yk_slot, true,
- CHALLENGELEN, (unsigned char *)challenge_new,
- RESPONSELEN, (unsigned char *)repose_new)) < 0) {
- perror("yk_challenge_response() failed");
- goto out40;
- }
- yubikey_hex_encode((char *)passphrase_new, (char *)repose_new, 20);
-
- /* initialize crypt device */
- if ((rc = crypt_init_by_name(&cd, device_name)) < 0) {
- fprintf(stderr, "Device %s failed to initialize.\n", device_name);
- goto out40;
- }
-
/* these are the filenames for challenge
* we need this for reading and writing */
sprintf(challengefilename, CHALLENGEDIR "/challenge-%d", serial);
@@ -162,29 +219,53 @@ int main(int argc, char **argv) {
/* write new challenge to file */
if ((rc = challengefiletmp = mkstemp(challengefiletmpname)) < 0) {
fprintf(stderr, "Could not open file %s for writing.\n", challengefiletmpname);
- goto out50;
+ goto out40;
}
if ((rc = write(challengefiletmp, challenge_new, CHALLENGELEN)) < 0) {
fprintf(stderr, "Failed to write challenge to file.\n");
- goto out60;
+ goto out50;
}
challengefiletmp = close(challengefiletmp);
+ /* now that the new challenge has been written to file...
+ * add second factor to new challenge */
+ if (second_factor) {
+ const char * tmp = new_2nd_factor ? new_2nd_factor : payload;
+ plen = strlen(tmp);
+ memcpy(challenge_new, tmp, plen < CHALLENGELEN / 2 ? plen : CHALLENGELEN / 2);
+ }
+
+ /* do challenge/response and encode to hex */
+ if ((rc = yk_challenge_response(yk, yk_slot, true,
+ CHALLENGELEN, (unsigned char *) challenge_new,
+ RESPONSELEN, (unsigned char *) response_new)) < 0) {
+ perror("yk_challenge_response() failed");
+ goto out50;
+ }
+ yubikey_hex_encode((char *) passphrase_new, (char *) response_new, SHA1_DIGEST_SIZE);
+
/* get status of crypt device
* We expect this to be active (or busy). It is the actual root device, no? */
- cs = crypt_status(cd, device_name);
- if (cs != CRYPT_ACTIVE && cs != CRYPT_BUSY) {
+ cryptstatus = crypt_status(cryptdevice, device_name);
+ if (cryptstatus != CRYPT_ACTIVE && cryptstatus != CRYPT_BUSY) {
rc = EXIT_FAILURE;
fprintf(stderr, "Device %s is invalid or inactive.\n", device_name);
goto out60;
}
- ck = crypt_keyslot_status(cd, luks_slot);
- if (ck == CRYPT_SLOT_INVALID) {
+ /* initialize crypt device */
+ if ((rc = crypt_init_by_name(&cryptdevice, device_name)) < 0) {
+ fprintf(stderr, "Device %s failed to initialize.\n", device_name);
+ goto out60;
+ }
+
+ cryptkeyslot = crypt_keyslot_status(cryptdevice, luks_slot);
+
+ if (cryptkeyslot == CRYPT_SLOT_INVALID) {
rc = EXIT_FAILURE;
fprintf(stderr, "Key slot %d is invalid.\n", luks_slot);
goto out60;
- } else if (ck == CRYPT_SLOT_ACTIVE || ck == CRYPT_SLOT_ACTIVE_LAST) {
+ } else if (cryptkeyslot == CRYPT_SLOT_ACTIVE || cryptkeyslot == CRYPT_SLOT_ACTIVE_LAST) {
/* read challenge from file */
if ((rc = challengefile = open(challengefilename, O_RDONLY)) < 0) {
perror("Failed opening challenge file for reading");
@@ -199,20 +280,38 @@ int main(int argc, char **argv) {
challengefile = close(challengefile);
/* finished reading challenge */
- /* do challenge/response and encode to hex */
- if ((rc = yk_challenge_response(yk, yk_slot, true,
- CHALLENGELEN, (unsigned char *)challenge_old,
- RESPONSELEN, (unsigned char *)repose_old)) < 0) {
- perror("yk_challenge_response() failed");
- goto out60;
+ /* create a copy for second run in loop */
+ memcpy(challenge_old_no_2f, challenge_old, CHALLENGELEN);
+
+ /* copy the second factor */
+ if (second_factor) {
+ plen = strlen(payload);
+ memcpy(challenge_old, payload, plen < CHALLENGELEN / 2 ? plen : CHALLENGELEN / 2);
}
- yubikey_hex_encode((char *)passphrase_old, (char *)repose_old, 20);
- if ((rc = crypt_keyslot_change_by_passphrase(cd, luks_slot, luks_slot,
- passphrase_old, PASSPHRASELEN,
- passphrase_new, PASSPHRASELEN)) < 0) {
- fprintf(stderr, "Could not update passphrase for key slot %d.\n", luks_slot);
- goto out60;
+ /* try old with and without 2nd factor */
+ for (uint8_t i = 0; i < 1 + second_factor; i++) {
+ /* do challenge/response and encode to hex */
+ if ((rc = yk_challenge_response(yk, yk_slot, true,
+ CHALLENGELEN, (unsigned char *) challenge_old,
+ RESPONSELEN, (unsigned char *) response_old)) < 0) {
+ perror("yk_challenge_response() failed");
+ goto out60;
+ }
+ yubikey_hex_encode((char *) passphrase_old, (char *) response_old, SHA1_DIGEST_SIZE);
+
+ if ((rc = crypt_keyslot_change_by_passphrase(cryptdevice, luks_slot, luks_slot,
+ passphrase_old, PASSPHRASELEN,
+ passphrase_new, PASSPHRASELEN)) < 0) {
+ fprintf(stderr, "Could not update passphrase for key slot %d on %s try.\n",
+ luks_slot, i ? "second" : "first");
+ if (!second_factor || i > 0)
+ goto out60;
+ } else
+ break;
+
+ /* copy back... */
+ memcpy(challenge_old, challenge_old_no_2f, CHALLENGELEN);
}
if ((rc = unlink(challengefilename)) < 0) {
@@ -220,7 +319,7 @@ int main(int argc, char **argv) {
goto out60;
}
} else { /* ck == CRYPT_SLOT_INACTIVE */
- if ((rc = crypt_keyslot_add_by_passphrase(cd, luks_slot, NULL, 0,
+ if ((rc = crypt_keyslot_add_by_passphrase(cryptdevice, luks_slot, NULL, 0,
passphrase_new, PASSPHRASELEN)) < 0) {
fprintf(stderr, "Could add passphrase for key slot %d.\n", luks_slot);
goto out60;
@@ -235,18 +334,18 @@ int main(int argc, char **argv) {
rc = EXIT_SUCCESS;
out60:
+ /* free crypt context */
+ crypt_free(cryptdevice);
+
+out50:
/* close the challenge file */
if (challengefile)
close(challengefile);
if (challengefiletmp)
close(challengefiletmp);
- if (access(challengefiletmpname, F_OK) == 0 )
+ if (access(challengefiletmpname, F_OK) == 0)
unlink(challengefiletmpname);
-out50:
- /* free crypt context */
- crypt_free(cd);
-
out40:
/* close Yubikey */
if (!yk_close_key(yk))
@@ -261,17 +360,20 @@ out20:
/* free iniparser dictionary */
iniparser_freedict(ini);
-
out10:
/* wipe response (cleartext password!) from memory */
/* This is statically allocated and always save to wipe! */
memset(challenge_old, 0, CHALLENGELEN + 1);
+ memset(challenge_old_no_2f, 0, CHALLENGELEN + 1);
memset(challenge_new, 0, CHALLENGELEN + 1);
- memset(repose_old, 0, RESPONSELEN);
- memset(repose_new, 0, RESPONSELEN);
+ memset(response_old, 0, RESPONSELEN);
+ memset(response_new, 0, RESPONSELEN);
memset(passphrase_old, 0, PASSPHRASELEN + 1);
memset(passphrase_new, 0, PASSPHRASELEN + 1);
+ free(new_2nd_factor);
+ free(payload);
+
return rc;
}
diff --git a/config.def.h b/config.def.h
index f77c141..23518be 100644
--- a/config.def.h
+++ b/config.def.h
@@ -21,6 +21,8 @@
#define CONFYKSLOT "yk slot"
/* config file LUKS slot */
#define CONFLUKSSLOT "luks slot"
+/* config file second factor */
+#define CONF2NDFACTOR "second factor"
/* path to cpio archive (initramfs image) */
#define CPIOFILE "/boot/ykfde-challenges.img"
diff --git a/mkinitcpio/ykfde-2f b/mkinitcpio/ykfde-2f
new file mode 100644
index 0000000..5e09dd9
--- /dev/null
+++ b/mkinitcpio/ykfde-2f
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+build() {
+ add_systemd_unit cryptsetup-pre.target
+ add_systemd_unit ykfde-2f.service
+ add_symlink "/usr/lib/systemd/system/sysinit.target.wants/ykfde-2f.service" "../ykfde-2f.service"
+ add_file /usr/lib/systemd/scripts/ykfde-2f
+ add_binary keyctl
+ add_binary systemd-ask-password
+}
+
+help() {
+ echo "This hook adds 2nd factor support for Yubikey full disk encryption."
+}
diff --git a/systemd/ykfde-2f b/systemd/ykfde-2f
new file mode 100644
index 0000000..3aac298
--- /dev/null
+++ b/systemd/ykfde-2f
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# (C) 2016 by Christian Hesse <mail@eworm.de>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+YKFDEFACTOR="$(systemd-ask-password --no-tty 'Please enter second factor for Yubikey full disk encryption!')"
+YKFDESERIAL="$(keyctl 'add' 'user' 'ykfde-2f' "${YKFDEFACTOR}" '@u')"
+keyctl 'timeout' "${YKFDESERIAL}" '150'
+
+if [ -s '/run/ykfde.pid' ]; then
+ kill -USR1 $(cat '/run/ykfde.pid')
+ # ykfde started from udev needs a moment to set up the key
+ # in store. It is out of systemd control, so wait a moment
+ # here.
+ sleep 0.2
+fi
+
+true
diff --git a/systemd/ykfde-2f.service b/systemd/ykfde-2f.service
new file mode 100644
index 0000000..acb6d67
--- /dev/null
+++ b/systemd/ykfde-2f.service
@@ -0,0 +1,16 @@
+# (C) 2016 by Christian Hesse <mail@eworm.de>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+[Unit]
+Description=Get 2nd Factor for YKFDE
+DefaultDependencies=no
+Before=cryptsetup-pre.target
+Wants=cryptsetup-pre.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+TimeoutSec=0
+ExecStart=/usr/lib/systemd/scripts/ykfde-2f
diff --git a/udev/Makefile b/udev/Makefile
index b2b8994..2ee1319 100644
--- a/udev/Makefile
+++ b/udev/Makefile
@@ -8,7 +8,7 @@ CFLAGS += -std=c11 -O2 -fpic -pie -Wall -Werror
all: ykfde
ykfde: ykfde.c ../config.h
- $(CC) $(CFLAGS) -lykpers-1 -lyubikey -liniparser $(LDFLAGS) -o ykfde ykfde.c
+ $(CC) $(CFLAGS) -liniparser -lkeyutils -lykpers-1 -lyubikey $(LDFLAGS) -o ykfde ykfde.c
install: ykfde
$(INSTALL) -D -m0755 ykfde $(DESTDIR)/usr/lib/udev/ykfde
diff --git a/udev/ykfde.c b/udev/ykfde.c
index bf53eb4..9e0d1d3 100644
--- a/udev/ykfde.c
+++ b/udev/ykfde.c
@@ -5,7 +5,7 @@
* of the GNU General Public License, incorporated herein by reference.
*
* compile with:
- * $ gcc -o ykfde ykfde.c -lykpers-1 -lyubikey -liniparser
+ * $ gcc -o ykfde ykfde.c -liniparser -lkeyutils -lykpers-1 -lyubikey
*
* test with:
* $ systemd-ask-password --no-tty "Please enter passphrase for disk foobar..."
@@ -15,36 +15,49 @@
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
+#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/inotify.h>
#include <sys/poll.h>
-#include <sys/signalfd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
+#include <iniparser.h>
+
+#include <keyutils.h>
+
#include <yubikey.h>
#include <ykpers-1/ykdef.h>
#include <ykpers-1/ykcore.h>
-#include <iniparser.h>
-
#include "../config.h"
-#define EVENT_SIZE (sizeof (struct inotify_event))
-#define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16))
-
-#define CHALLENGELEN 64
+/* Yubikey supports write of 64 byte challenge to slot,
+ * returns HMAC-SHA1 response.
+ *
+ * Lengths are defined in ykpers-1/ykdef.h:
+ * SHA1_MAX_BLOCK_SIZE 64
+ * SHA1_DIGEST_SIZE 20
+ *
+ * For passphrase we use hex encoded digest, that is
+ * twice the length of binary digest. */
+#define CHALLENGELEN SHA1_MAX_BLOCK_SIZE
#define RESPONSELEN SHA1_MAX_BLOCK_SIZE
#define PASSPHRASELEN SHA1_DIGEST_SIZE * 2
#define ASK_PATH "/run/systemd/ask-password/"
#define ASK_MESSAGE "Please enter passphrase for disk"
+#define PID_PATH "/run/ykfde.pid"
+
+void received_signal(int signal) {
+ /* Do nothing, just interrupt the sleep. */
+ return;
+}
static int send_on_socket(int fd, const char *socket_name, const void *packet, size_t size) {
union {
@@ -64,50 +77,121 @@ static int send_on_socket(int fd, const char *socket_name, const void *packet, s
return EXIT_SUCCESS;
}
-static int try_answer(char * ask_file, char * response) {
+static int try_answer(YK_KEY * yk, uint8_t slot, const char * ask_file, char * challenge) {
int8_t rc = EXIT_FAILURE;
dictionary * ini;
const char * ask_message, * ask_socket;
int fd_askpass;
+ char response[RESPONSELEN],
+ passphrase[PASSPHRASELEN + 1],
+ passphrase_askpass[PASSPHRASELEN + 2];
+ /* keyutils */
+ key_serial_t key;
+ void * payload = NULL;
+ size_t plen;
+
+ memset(response, 0, RESPONSELEN);
+ memset(passphrase, 0, PASSPHRASELEN + 1);
+ memset(passphrase_askpass, 0, PASSPHRASELEN + 2);
+
+ /* get second factor from key store
+ * if this fails it is not critical... possibly we just do not
+ * use second factor */
+ key = request_key("user", "ykfde-2f", NULL, 0);
+
+ if (key > 0) {
+ /* if we have a key id we have a key - so this should succeed */
+ if ((rc = keyctl_read_alloc(key, &payload)) < 0) {
+ perror("Failed reading payload from key");
+ goto out1;
+ }
+
+ /* we replace part of the challenge with the second factor */
+ plen = strlen(payload);
+ memcpy(challenge, payload, plen < CHALLENGELEN / 2 ? plen : CHALLENGELEN / 2);
+
+ free(payload);
+ }
+
+ /* do challenge/response and encode to hex */
+ if ((rc = yk_challenge_response(yk, slot, true,
+ CHALLENGELEN, (unsigned char *) challenge,
+ RESPONSELEN, (unsigned char *) response)) < 0) {
+ perror("yk_challenge_response() failed");
+ goto out1;
+ }
+ yubikey_hex_encode((char *) passphrase, (char *) response, SHA1_DIGEST_SIZE);
+
+ /* add key to kernel key store */
+ if ((key = add_key("user", "cryptsetup", passphrase, PASSPHRASELEN, KEY_SPEC_USER_KEYRING)) > 0) {
+ if (keyctl_set_timeout(key, 150) < 0)
+ perror("keyctl_set_timeout() failed");
+ } else
+ perror("add_key() failed");
+
+ /* key is placed, no ask file... quit here */
+ if (ask_file == NULL) {
+ rc = key > 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+ goto out1;
+ }
- if ((ini = iniparser_load(ask_file)) == NULL)
+ if ((ini = iniparser_load(ask_file)) == NULL) {
+ rc = EXIT_FAILURE;
perror("cannot parse file");
+ goto out1;
+ }
ask_message = iniparser_getstring(ini, "Ask:Message", NULL);
- if (strncmp(ask_message, ASK_MESSAGE, strlen(ASK_MESSAGE)) != 0)
- goto out1;
+ if (strncmp(ask_message, ASK_MESSAGE, strlen(ASK_MESSAGE)) != 0) {
+ rc = EXIT_FAILURE;
+ goto out2;
+ }
- ask_socket = iniparser_getstring(ini, "Ask:Socket", NULL);
+ if ((ask_socket = iniparser_getstring(ini, "Ask:Socket", NULL)) == NULL) {
+ rc = EXIT_FAILURE;
+ perror("Could not get socket name");
+ goto out2;
+ }
+
+ sprintf(passphrase_askpass, "+%s", passphrase);
if ((fd_askpass = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
+ rc = EXIT_FAILURE;
perror("socket() failed");
- goto out1;
+ goto out2;
}
- if (send_on_socket(fd_askpass, ask_socket, response, PASSPHRASELEN + 1) < 0) {
+ if (send_on_socket(fd_askpass, ask_socket, passphrase_askpass, PASSPHRASELEN + 1) < 0) {
+ rc = EXIT_FAILURE;
perror("send_on_socket() failed");
- goto out2;
+ goto out3;
}
rc = EXIT_SUCCESS;
-out2:
+out3:
close(fd_askpass);
-out1:
+out2:
iniparser_freedict(ini);
+out1:
+ /* wipe response (cleartext password!) from memory */
+ memset(response, 0, RESPONSELEN);
+ memset(passphrase, 0, PASSPHRASELEN + 1);
+ memset(passphrase_askpass, 0, PASSPHRASELEN + 2);
+
return rc;
}
int main(int argc, char **argv) {
int8_t rc = EXIT_FAILURE;
+ FILE *pidfile;
/* Yubikey */
YK_KEY * yk;
uint8_t slot = SLOT_CHAL_HMAC2;
unsigned int serial = 0;
- char response[RESPONSELEN], passphrase[PASSPHRASELEN + 1], passphrase_askpass[PASSPHRASELEN + 2];
/* iniparser */
dictionary * ini;
char section_ykslot[10 /* unsigned int in char */ + 1 + sizeof(CONFYKSLOT) + 1];
@@ -118,17 +202,32 @@ int main(int argc, char **argv) {
/* read dir */
DIR * dir;
struct dirent * ent;
- /* inotify */
- struct inotify_event * event;
- int fd_inotify, watch, length, i = 0;
- char buffer[EVENT_BUF_LEN];
-#if DEBUG
+#ifdef DEBUG
/* reopening stderr to /dev/console may help debugging... */
- freopen("/dev/console", "w", stderr);
+ FILE * tmp = freopen("/dev/console", "w", stderr);
+ (void) tmp;
#endif
- memset(challenge, 0, CHALLENGELEN);
+ if ((pidfile = fopen(PID_PATH, "w")) != NULL) {
+ if (fprintf(pidfile, "%d", getpid()) < 0) {
+ rc = EXIT_FAILURE;
+ perror("Failed writing pid");
+ fclose(pidfile);
+ goto out10;
+ }
+ fclose(pidfile);
+ } else {
+ rc = EXIT_FAILURE;
+ perror("Failed opening pid file");
+ goto out10;
+ }
+
+ /* connect to signal */
+ signal(SIGUSR1, received_signal);
+
+ /* initialize static memory */
+ memset(challenge, 0, CHALLENGELEN + 1);
/* init and open first Yubikey */
if ((rc = yk_init()) < 0) {
@@ -143,7 +242,7 @@ int main(int argc, char **argv) {
}
/* read the serial number from key */
- if((rc = !yk_get_serial(yk, 0, 0, &serial)) < 0) {
+ if ((rc = yk_get_serial(yk, 0, 0, &serial)) < 0) {
perror("yk_get_serial() failed");
goto out30;
}
@@ -151,8 +250,10 @@ int main(int argc, char **argv) {
sprintf(challengefilename, CHALLENGEDIR "/challenge-%d", serial);
/* check if challenge file exists */
- if (access(challengefilename, R_OK) == -1)
+ if (access(challengefilename, R_OK) == -1) {
+ rc = EXIT_FAILURE;
goto out30;
+ }
/* read challenge from file */
if ((rc = challengefile = open(challengefilename, O_RDONLY)) < 0) {
@@ -162,10 +263,8 @@ int main(int argc, char **argv) {
if ((rc = read(challengefile, challenge, CHALLENGELEN)) < 0) {
perror("Failed reading challenge from file");
- goto out50;
+ goto out40;
}
- challengefile = close(challengefile);
- /* finished reading challenge */
/* try to read config file
* if anything here fails we do not care... slot 2 is the default */
@@ -193,75 +292,39 @@ int main(int argc, char **argv) {
iniparser_freedict(ini);
}
- memset(response, 0, RESPONSELEN);
- memset(passphrase, 0, PASSPHRASELEN + 1);
-
- /* do challenge/response and encode to hex */
- if ((rc = yk_challenge_response(yk, slot, true,
- CHALLENGELEN, (unsigned char *)challenge,
- RESPONSELEN, (unsigned char *)response)) < 0) {
- perror("yk_challenge_response() failed");
- goto out50;
- }
- yubikey_hex_encode((char *)passphrase, (char *)response, 20);
-
- sprintf(passphrase_askpass, "+%s", passphrase);
-
/* change to directory so we do not have to assemble complete/absolute path */
if ((rc = chdir(ASK_PATH)) != 0) {
perror("chdir() failed");
- goto out50;
- }
-
- /* creating the INOTIFY instance and add ASK_PATH directory into watch list */
- if ((rc = fd_inotify = inotify_init()) < 0) {
- perror("inotify_init() failed");
- goto out50;
+ goto out40;
}
- watch = inotify_add_watch(fd_inotify, ASK_PATH, IN_MOVED_TO);
-
- /* Is the request already there?
- * We do this AFTER setting up the inotify watch. This way we do not have race condition. */
+ /* Is the request already there? */
if ((dir = opendir(ASK_PATH)) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (strncmp(ent->d_name, "ask.", 4) == 0) {
- if ((rc = try_answer(ent->d_name, passphrase_askpass)) == EXIT_SUCCESS)
- goto out70;
+ if ((rc = try_answer(yk, slot, ent->d_name, challenge)) == EXIT_SUCCESS)
+ goto out50;
}
}
} else {
rc = EXIT_FAILURE;
perror ("opendir() failed");
- goto out60;
+ goto out50;
}
- /* read to determine the event change happens. Actually this read blocks until the change event occurs */
- if ((rc = length = read(fd_inotify, buffer, EVENT_BUF_LEN)) < 0) {
- perror("read() failed");
- goto out70;
- }
+ /* Wait for 90 seconds.
+ * The user has this time to enter his second factor, resulting in
+ * SIGUSR1 being sent to us. */
+ sleep(90);
- /* actually read return the list of change events happens.
- * Here, read the change event one by one and process it accordingly. */
- while (i < length) {
- event = (struct inotify_event *)&buffer[i];
- if (event->len > 0)
- if ((rc = try_answer(event->name, passphrase_askpass)) == EXIT_SUCCESS)
- goto out70;
- i += EVENT_SIZE + event->len;
- }
+ /* try again, but for key store this time */
+ rc = try_answer(yk, slot, NULL, challenge);
-out70:
+out50:
/* close dir */
closedir(dir);
-out60:
- /* remove inotify watch and remove file handle */
- inotify_rm_watch(fd_inotify, watch);
- close(fd_inotify);
-
-out50:
+out40:
/* close the challenge file */
if (challengefile)
close(challengefile);
@@ -270,12 +333,6 @@ out50:
unlink(challengefilename);
out30:
- /* wipe response (cleartext password!) from memory */
- memset(challenge, 0, CHALLENGELEN);
- memset(response, 0, RESPONSELEN);
- memset(passphrase, 0, PASSPHRASELEN + 1);
- memset(passphrase_askpass, 0, PASSPHRASELEN + 2);
-
/* close Yubikey */
if (yk_close_key(yk) < 0)
perror("yk_close_key() failed");
@@ -286,6 +343,9 @@ out20:
perror("yk_release() failed");
out10:
+ /* wipe challenge from memory */
+ memset(challenge, 0, CHALLENGELEN + 1);
+
return rc;
}