summaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rw-r--r--bin/Makefile17
-rwxr-xr-xbin/ykfde74
-rw-r--r--bin/ykfde.c280
3 files changed, 297 insertions, 74 deletions
diff --git a/bin/Makefile b/bin/Makefile
new file mode 100644
index 0000000..daa77f2
--- /dev/null
+++ b/bin/Makefile
@@ -0,0 +1,17 @@
+# commands
+CC := gcc
+INSTALL := install
+RM := rm
+# flags
+CFLAGS += -std=c11 -O2 -fpic -pie -Wall -Werror
+
+all: ykfde
+
+ykfde: ykfde.c
+ $(CC) $(CFLAGS) -lykpers-1 -lyubikey -liniparser -lcryptsetup $(LDFLAGS) -o ykfde ykfde.c
+
+install: ykfde
+ $(INSTALL) -D -m0755 ykfde $(DESTDIR)/usr/bin/ykfde
+
+clean:
+ $(RM) -f ykfde
diff --git a/bin/ykfde b/bin/ykfde
deleted file mode 100755
index 41601b1..0000000
--- a/bin/ykfde
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/bin/sh
-
-function help() {
- echo "usage: ${0} [OPTIONS]"
- echo
- echo "where OPTIONS are:"
- echo " -1 use Yubico key slot 1"
- echo " -2 use Yubico key slot 2 (default)"
- echo " -h show this help"
-}
-
-DEVICE="$(egrep -v '^(#|$)' /etc/crypttab.initramfs 2>/dev/null | head -n1 | sed 's/\s\+/:/g' | cut -d: -f2)"
-SERIAL="$(ykinfo -sq)"
-SLOT="2"
-TMPDIR="$(mktemp --directory --tmpdir=/tmp/ .$(basename ${0})-${$}-XXXXXX)"
-
-while getopts "12h" opt; do
- case ${opt} in
- 1)
- SLOT="1"
- ;;
- 2)
- SLOT="2"
- ;;
- h)
- help
- exit 0
- ;;
- esac
-done
-
-# check we have all information
-if [ -z "${DEVICE}" ]; then
- echo "Failed to get device from /etc/crypttab.initramfs." >&2
- exit 1
-elif [ ! -b "${DEVICE}" ]; then
- echo "Device '${DEVICE}' does not exist or is not a block device." >&2
- exit 1
-elif ! cryptsetup isLuks "${DEVICE}" 2>/dev/null; then
- echo "Device '${DEVICE}' is not a LUKS device." >&2
- exit 1
-elif [ -z "${SERIAL}" ]; then
- echo "Did not get a serial number from key. Did you insert one?" >&2
- exit 1
-fi
-
-# This directroy should exist, but we create it in case it does not
-if [ ! -d "/etc/ykfde.d/" ]; then
- install -d -m 0700 "/etc/ykfde.d/"
-fi
-
-# generate the challenge
-if ! makepasswd --chars=64 | tr -d '\n' > "/etc/ykfde.d/challenge-${SERIAL}"; then
- exit 1
-fi
-
-# generate response
-if ! ykchalresp -${SLOT} "$(cat "/etc/ykfde.d/challenge-${SERIAL}")" | tr -d '\n' > "${TMPDIR}/ykfde-response"; then
- # ykchalresp should have shouted, so do not complain here
- exit 1
-fi
-
-# add key to LUKS device
-if ! cryptsetup luksAddKey "${DEVICE}" "${TMPDIR}/ykfde-response"; then
- # cryptsetup should have shouted, ...
- exit 1
-fi
-
-# shred response and remove temporary directory
-shred --remove "${TMPDIR}/ykfde-response"
-rm -rf "${TMPDIR}"
-
-echo "Please do not forget to remove old keys when changing challenge!"
-echo "Now run 'mkinitcpio' to build a new initramfs!"
diff --git a/bin/ykfde.c b/bin/ykfde.c
new file mode 100644
index 0000000..43735d5
--- /dev/null
+++ b/bin/ykfde.c
@@ -0,0 +1,280 @@
+/*
+ * (C) 2014 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.
+ *
+ * compile with:
+ * $ gcc -o ykfde ykfde.c -lykpers-1 -lyubikey -lcryptsetup -liniparser
+ */
+
+#ifndef _DEFAULT_SOURCE
+#define _DEFAULT_SOURCE
+#endif
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <iniparser.h>
+
+#include <yubikey.h>
+#include <ykpers-1/ykdef.h>
+#include <ykpers-1/ykcore.h>
+
+#include <libcryptsetup.h>
+
+/* challenge is 64 byte,
+ * HMAC-SHA1 response is 40 byte */
+#define CHALLENGELEN 64
+#define RESPONSELEN SHA1_MAX_BLOCK_SIZE
+#define PASSPHRASELEN SHA1_DIGEST_SIZE * 2
+
+#define CONFIGFILE "/etc/ykfde.conf"
+#define CHALLENGEDIR "/etc/ykfde.d/"
+#define CONFYKSLOT "yk slot"
+#define CONFLUKSSLOT "luks slot"
+#define CONFDEVNAME "device name"
+
+int main(int argc, char **argv) {
+ char challenge_old[CHALLENGELEN + 1],
+ challenge_new[CHALLENGELEN + 1],
+ repose_old[RESPONSELEN],
+ repose_new[RESPONSELEN],
+ passphrase_old[PASSPHRASELEN + 1],
+ passphrase_new[PASSPHRASELEN + 1];
+ char challengefilename[sizeof(CHALLENGEDIR) + 11 /* "/challenge-" */ + 10 /* unsigned int in char */ + 1],
+ challengefiletmpname[sizeof(CHALLENGEDIR) + 11 /* "/challenge-" */ + 10 /* unsigned int in char */ + 7 /* -XXXXXX */ + 1];
+ int challengefile = 0, challengefiletmp = 0;
+ struct timeval tv;
+ int i;
+ int8_t rc = EXIT_FAILURE;
+ /* cryptsetup */
+ char * device_name;
+ int8_t luks_slot = -1;
+ struct crypt_device *cd;
+ crypt_status_info cs;
+ crypt_keyslot_info ck;
+ /* yubikey */
+ YK_KEY * yk;
+ uint8_t yk_slot = SLOT_CHAL_HMAC2;
+ unsigned int serial = 0;
+ /* 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];
+
+ /* initialize random seed */
+ gettimeofday(&tv, NULL);
+ srand(tv.tv_usec * tv.tv_sec);
+
+ memset(challenge_old, 0, CHALLENGELEN + 1);
+ memset(challenge_new, 0, CHALLENGELEN + 1);
+ memset(repose_old, 0, RESPONSELEN);
+ memset(repose_new, 0, RESPONSELEN);
+ memset(passphrase_old, 0, PASSPHRASELEN + 1);
+ memset(passphrase_new, 0, PASSPHRASELEN + 1);
+
+ if ((ini = iniparser_load(CONFIGFILE)) == NULL) {
+ rc = EXIT_FAILURE;
+ fprintf(stderr, "Could not parse configuration file.\n");
+ goto out10;
+ }
+
+ if ((device_name = iniparser_getstring(ini, "general:" CONFDEVNAME, NULL)) == NULL) {
+ rc = EXIT_FAILURE;
+ /* read from crypttab? */
+ /* get device from currently open devices? */
+ fprintf(stderr, "Could not read LUKS device from configuration file.\n");
+ goto out20;
+ }
+
+ /* init and open first Yubikey */
+ if ((rc = yk_init()) < 0) {
+ perror("yk_init() failed");
+ goto out20;
+ }
+
+ if ((yk = yk_open_first_key()) == NULL) {
+ rc = EXIT_FAILURE;
+ perror("yk_open_first_key() failed");
+ goto out30;
+ }
+
+ /* read the serial number from key */
+ if ((rc = yk_get_serial(yk, 0, 0, &serial)) < 0) {
+ perror("yk_get_serial() failed");
+ goto out40;
+ }
+
+ /* get the yk slot */
+ sprintf(section_ykslot, "%d:" CONFYKSLOT, serial);
+ yk_slot = iniparser_getint(ini, "general:" CONFYKSLOT, yk_slot);
+ yk_slot = iniparser_getint(ini, section_ykslot, yk_slot);
+ switch (yk_slot) {
+ case 1:
+ case SLOT_CHAL_HMAC1:
+ yk_slot = SLOT_CHAL_HMAC1;
+ break;
+ case 2:
+ case SLOT_CHAL_HMAC2:
+ default:
+ yk_slot = SLOT_CHAL_HMAC2;
+ break;
+ }
+
+ /* get the luks slot */
+ sprintf(section_luksslot, "%d:" CONFLUKSSLOT, serial);
+ 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);
+ goto out40;
+ }
+
+ /* 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);
+ sprintf(challengefiletmpname, CHALLENGEDIR "/challenge-%d-XXXXXX", serial);
+
+ /* write new challenge to file */
+ if ((rc = challengefiletmp = mkstemp(challengefiletmpname)) < 0) {
+ fprintf(stderr, "Could not open file %s for writing.\n", challengefiletmpname);
+ goto out50;
+ }
+ if ((rc = write(challengefiletmp, challenge_new, CHALLENGELEN)) < 0) {
+ fprintf(stderr, "Failed to write challenge to file.\n");
+ goto out60;
+ }
+ challengefiletmp = close(challengefiletmp);
+
+ /* 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) {
+ 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) {
+ 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) {
+ /* read challenge from file */
+ if ((rc = challengefile = open(challengefilename, O_RDONLY)) < 0) {
+ perror("Failed opening challenge file for reading");
+ goto out60;
+ }
+
+ if ((rc = read(challengefile, challenge_old, CHALLENGELEN)) < 0) {
+ perror("Failed reading challenge from file");
+ goto out60;
+ }
+
+ 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;
+ }
+ 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;
+ }
+
+ if ((rc = unlink(challengefilename)) < 0) {
+ fprintf(stderr, "Failed to delete old challenge file.\n");
+ goto out60;
+ }
+ } else { /* ck == CRYPT_SLOT_INACTIVE */
+ if ((rc = crypt_keyslot_add_by_passphrase(cd, luks_slot, NULL, 0,
+ passphrase_new, PASSPHRASELEN)) < 0) {
+ fprintf(stderr, "Could add passphrase for key slot %d.\n", luks_slot);
+ goto out60;
+ }
+ }
+
+ if ((rc = rename(challengefiletmpname, challengefilename)) < 0) {
+ fprintf(stderr, "Failed to rename new challenge file.\n");
+ goto out60;
+ }
+
+ rc = EXIT_SUCCESS;
+
+out60:
+ /* close the challenge file */
+ if (challengefile)
+ close(challengefile);
+ if (challengefiletmp)
+ close(challengefiletmp);
+ if (access(challengefiletmpname, F_OK ) == 0 ) {
+ unlink(challengefiletmpname);
+ }
+
+out50:
+ /* free crypt context */
+ crypt_free(cd);
+
+out40:
+ /* close Yubikey */
+ if (!yk_close_key(yk))
+ perror("yk_close_key() failed");
+
+out30:
+ /* release Yubikey */
+ if (!yk_release())
+ perror("yk_release() failed");
+
+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_new, 0, CHALLENGELEN + 1);
+ memset(repose_old, 0, RESPONSELEN);
+ memset(repose_new, 0, RESPONSELEN);
+ memset(passphrase_old, 0, PASSPHRASELEN + 1);
+ memset(passphrase_new, 0, PASSPHRASELEN + 1);
+
+ return rc;
+}
+
+// vim: set syntax=c: