From 40a0f31f1838d4774ebd960640bfb230dc562ea1 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Sat, 16 Jan 2016 02:13:22 +0100 Subject: We have support for second factor. Yeah! --- bin/Makefile | 6 +- bin/ykfde.c | 230 ++++++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 169 insertions(+), 67 deletions(-) (limited to 'bin') 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 +#include #include #include #include @@ -24,6 +18,8 @@ #include +#include + #include #include #include @@ -31,18 +27,40 @@ #include #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 ] [-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; } -- cgit v1.2.3-54-g00ecf