From 11bd572adf4a861e4c42123c2dadbacd3349af93 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Tue, 23 Dec 2014 18:25:17 +0100 Subject: support updating the challenge on boot --- .gitignore | 1 + Makefile | 2 +- README.md | 27 +++++-- bin/Makefile | 10 ++- bin/ykfde-cpio.c | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++ bin/ykfde.c | 3 +- mkinitcpio/ykfde-cpio | 15 ++++ systemd/ykfde.service | 11 +++ 8 files changed, 276 insertions(+), 13 deletions(-) create mode 100644 bin/ykfde-cpio.c create mode 100644 mkinitcpio/ykfde-cpio create mode 100644 systemd/ykfde.service diff --git a/.gitignore b/.gitignore index 4da39b0..4809ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ README.html bin/ykfde +bin/ykfde-cpio udev/ykfde diff --git a/Makefile b/Makefile index 7ae3bce..3fe4aeb 100644 --- a/Makefile +++ b/Makefile @@ -23,8 +23,8 @@ install-bin: bin/ykfde udev/ykfde $(MAKE) -C bin install $(MAKE) -C udev install $(INSTALL) -D -m0644 conf/ykfde.conf $(DESTDIR)/etc/ykfde.conf - $(INSTALL) -D -m0755 bin/ykfde $(DESTDIR)/usr/bin/ykfde $(INSTALL) -D -m0644 mkinitcpio/ykfde $(DESTDIR)/usr/lib/initcpio/install/ykfde + $(INSTALL) -D -m0644 systemd/ykfde.service $(DESTDIR)/usr/lib/systemd/system/ykfde.service $(INSTALL) -d -m0700 $(DESTDIR)/etc/ykfde.d/ install-doc: README.md README.html diff --git a/README.md b/README.md index 6f47e94..2380003 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ To compile and use yubico full disk encryption you need: * [mkinitcpio](https://projects.archlinux.org/mkinitcpio.git/) (Though it may be easy to port this to any initramfs that uses systemd) * [markdown](http://daringfireball.net/projects/markdown/) (HTML documentation) +* [libarchive](http://www.libarchive.org/) (Update challenge on boot) Additionally it is expected to have `make` and `pkg-config` around to successfully compile. @@ -64,24 +65,36 @@ After that run: > ykfde This will store a challenge in `/etc/ykfde.d/` and add a new slot to -your LUKS device. Last add `ykfde` to your hook list in -`/etc/mkinitcpio.conf` and rebuild your initramfs with: +your LUKS device. Now you have two choices: + +### `ykfde` hook + +Last add `ykfde` to your hook list in `/etc/mkinitcpio.conf` and rebuild +your initramfs with: > mkinitcpio -p linux Reboot and have fun! +### `ykfde-cpio` hook + +Add `ykfde-cpio` to your hook list in `/etc/mkinitcpio.conf` and rebuild +your initramfs with: + +> mkinitcpio -p linux + +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). + +Reboot and have fun! + Limitation / TODO ----------------- * At the moment this is specific to Arch Linux. Though everything should run with upstream `systemd` just fine anybody has to hook things up with [dracut](https://dracut.wiki.kernel.org/) or whatever. -* The challenge is not updated on boot. The file is accessible read only in - initramfs, but we have no easy way to write it to persistant storage. - So probably this is a design limitation... However the install hook does - update the challenge when building a new initramfs and and Yubikey is - inserted. ### Upstream diff --git a/bin/Makefile b/bin/Makefile index daa77f2..4fcd6fb 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -5,13 +5,17 @@ RM := rm # flags CFLAGS += -std=c11 -O2 -fpic -pie -Wall -Werror -all: ykfde +all: ykfde ykfde-cpio ykfde: ykfde.c $(CC) $(CFLAGS) -lykpers-1 -lyubikey -liniparser -lcryptsetup $(LDFLAGS) -o ykfde ykfde.c -install: ykfde +ykfde-cpio: ykfde-cpio.c + $(CC) $(CFLAGS) -larchive $(LDFLAGS) -o ykfde-cpio ykfde-cpio.c + +install: ykfde ykfde-cpio $(INSTALL) -D -m0755 ykfde $(DESTDIR)/usr/bin/ykfde + $(INSTALL) -D -m0755 ykfde-cpio $(DESTDIR)/usr/bin/ykfde-cpio clean: - $(RM) -f ykfde + $(RM) -f ykfde ykfde-cpio diff --git a/bin/ykfde-cpio.c b/bin/ykfde-cpio.c new file mode 100644 index 0000000..71a39db --- /dev/null +++ b/bin/ykfde-cpio.c @@ -0,0 +1,220 @@ +/* + * (C) 2014 by Christian Hesse + * + * 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 mkcpio mkcpio.c -larchive + */ + +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define CONFIGFILE "/etc/ykfde.conf" +#define CHALLENGEDIR "/etc/ykfde.d/" +#define CPIOFILE "/boot/ykfde-challenges.img" +#define CPIOTMPFILE CPIOFILE "-XXXXXX" + +int add_dir(struct archive *archive, const char * path) { + struct stat st; + struct archive_entry *entry; + int8_t rc; + + /* initialize struct stat for directories from root */ + if ((rc = stat("/", &st)) < 0) { + perror("stat() failed"); + goto out; + } + + if ((entry = archive_entry_new()) == NULL) { + rc = EXIT_FAILURE; + fprintf(stderr, "archive_entry_new() failed"); + goto out; + } + + archive_entry_set_pathname(entry, path); + archive_entry_set_filetype(entry, AE_IFDIR); + archive_entry_copy_stat(entry, &st); + if (archive_write_header(archive, entry) != ARCHIVE_OK) { + rc = EXIT_FAILURE; + fprintf(stderr, "archive_write_header() failed"); + goto out; + } + archive_entry_free(entry); + + rc = EXIT_SUCCESS; + +out: + return rc; +} + +int main(int argc, const char **argv) { + char cpiotmpfile[] = CPIOTMPFILE; + struct archive *archive; + struct archive_entry *entry; + struct stat st; + char buff[64]; + int len, fdfile, fdarchive; + DIR * dir; + struct dirent * ent; + char * filename, * path; + off_t pathlength = 0; + int8_t rc = EXIT_FAILURE; + + if ((rc = fdarchive = mkstemp(cpiotmpfile)) < 0) { + perror("mkstemp() failed"); + goto out10; + } + + if ((archive = archive_write_new()) == NULL) { + rc = EXIT_FAILURE; + fprintf(stderr, "archive_write_new() failed.\n"); + goto out10; + } + + if (archive_write_set_format_cpio_newc(archive) != ARCHIVE_OK) { + rc = EXIT_FAILURE; + fprintf(stderr, "archive_write_set_format_cpio_newc() failed.\n"); + goto out10; + } + + if (archive_write_open_fd(archive, fdarchive) != ARCHIVE_OK) { + rc = EXIT_FAILURE; + fprintf(stderr, "archive_write_open_fd() failed.\n"); + goto out10; + } + + if ((rc = add_dir(archive, ".")) < 0) { + fprintf(stderr, "add_dir() failed"); + goto out10; + } + + while (1) { + path = strdup(CHALLENGEDIR + 1); + if (strstr(path + pathlength, "/") == NULL) + break; + *strstr(path + pathlength, "/") = 0; + pathlength = strlen(path) + 1; + + if ((rc = add_dir(archive, path)) < 0) { + fprintf(stderr, "add_dir() failed"); + goto out10; + } + + free(path); + } + + if ((dir = opendir(CHALLENGEDIR)) != NULL) { + while ((ent = readdir(dir)) != NULL) { + filename = malloc(sizeof(CHALLENGEDIR) + strlen(ent->d_name) + 1); + sprintf(filename, CHALLENGEDIR "%s", ent->d_name); + + if ((rc = stat(filename, &st)) < 0) { + perror("stat() failed"); + goto out10; + } + + if (S_ISREG(st.st_mode)) { + if ((entry = archive_entry_new()) == NULL) { + rc = EXIT_FAILURE; + fprintf(stderr, "archive_entry_new() failed.\n"); + goto out10; + } + + /* these do not return exit code */ + archive_entry_set_pathname(entry, filename + 1); + archive_entry_set_size(entry, st.st_size); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + + if (archive_write_header(archive, entry) != ARCHIVE_OK) { + rc = EXIT_FAILURE; + fprintf(stderr, "archive_write_header() failed"); + goto out10; + } + + if ((rc = fdfile = open(filename, O_RDONLY)) < 0) { + perror("open() failed"); + goto out10; + } + + if ((rc = len = read(fdfile, buff, sizeof(buff))) < 0) { + perror("read() failed"); + goto out10; + } + + while (len > 0) { + if (( rc = archive_write_data(archive, buff, len)) < 0) { + fprintf(stderr, "archive_write_data() failed"); + goto out10; + } + + if ((rc = len = read(fdfile, buff, sizeof(buff))) < 0) { + perror("read() failed"); + goto out10; + } + } + + if ((rc = close(fdfile)) < 0) { + perror("close() failed"); + goto out10; + } + + archive_entry_free(entry); + } + free(filename); + } + if ((rc = closedir(dir)) < 0) { + perror("closedir() failed"); + goto out10; + } + } else { + rc = EXIT_FAILURE; + perror("opendir() failed"); + goto out10; + } + + if (archive_write_close(archive) != ARCHIVE_OK) { + rc = EXIT_FAILURE; + fprintf(stderr, "archive_write_close() failed"); + goto out10; + } + + if (archive_write_free(archive) != ARCHIVE_OK) { + rc = EXIT_FAILURE; + fprintf(stderr, "archive_write_free() failed"); + goto out10; + } + + if (access(CPIOFILE, F_OK) == 0 && (rc = unlink(CPIOFILE)) < 0) { + perror("unkink() failed"); + goto out10; + } + + if ((rc = rename(cpiotmpfile, CPIOFILE)) < 0) { + perror("rename() failed"); + goto out10; + } + + rc = EXIT_SUCCESS; + +out10: + if (access(cpiotmpfile, F_OK) == 0) + unlink(cpiotmpfile); + + return rc; +} + +// vim: set syntax=c: diff --git a/bin/ykfde.c b/bin/ykfde.c index 43735d5..77bb86b 100644 --- a/bin/ykfde.c +++ b/bin/ykfde.c @@ -241,9 +241,8 @@ out60: close(challengefile); if (challengefiletmp) close(challengefiletmp); - if (access(challengefiletmpname, F_OK ) == 0 ) { + if (access(challengefiletmpname, F_OK) == 0 ) unlink(challengefiletmpname); - } out50: /* free crypt context */ diff --git a/mkinitcpio/ykfde-cpio b/mkinitcpio/ykfde-cpio new file mode 100644 index 0000000..c055a6d --- /dev/null +++ b/mkinitcpio/ykfde-cpio @@ -0,0 +1,15 @@ +#!/bin/sh + +build() { + # install files to initramfs + add_binary /usr/lib/udev/ykfde + add_file /usr/lib/initcpio/udev/20-ykfde.rules /usr/lib/udev/rules.d/20-ykfde.rules + add_file /etc/ykfde.conf +} + +help() { + echo "This hook adds support for opening LUKS devices with Yubico key." + echo "Please use command 'ykfde' to prepare." + echo "The hook ykfde-cpio does NOT include challenges! Please load" + echo "additional cpio archive with bootloader." +} diff --git a/systemd/ykfde.service b/systemd/ykfde.service new file mode 100644 index 0000000..36566a1 --- /dev/null +++ b/systemd/ykfde.service @@ -0,0 +1,11 @@ +[Unit] +Description=Yubikey full disk encryption + +[Service] +Type=oneshot +ExecStart=/usr/bin/ykfde +ExecStart=/usr/bin/ykfde-cpio +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3-54-g00ecf