diff options
Diffstat (limited to 'bin')
-rw-r--r-- | bin/Makefile | 17 | ||||
-rwxr-xr-x | bin/ykfde | 74 | ||||
-rw-r--r-- | bin/ykfde.c | 280 |
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: |