C sesame-setup implementation
David Zeuthen
david at fubar.dk
Fri Dec 31 05:39:22 PST 2004
Hi,
Sorry for the lag - I've been on holidays; slowly returning now :-)
On Tue, 2004-12-21 at 23:08 -0600, W. Michael Petullo wrote:
> I wrote a patch for the sesame code in CVS that adds a C implementation
> of sesame-setup. I used OpenSSL's libraries for the crypto because this
> is what I am familiar with. Cryptsetup is also required. It seems
> to work.
>
> Here are the steps one may take to initialize a device for use with
> sesame-setup:
>
> 1. Print out an encrypted random key (you will be prompted for a passphrase):
>
> echo -n `dd if=/dev/urandom bs=1c count=128`SESAME0 | openssl enc -aes-256-ecb | xxd -p -c 256
>
> NOTE: The above command line sometime creates less than 135 key bytes.
> One may need to run it a few times to get 128 + SESAME0 = 135 key bytes.
> You should be able to check with somethine like this (output should
> be 128):
>
> echo -n 53616c7465645f5faa47ebfa21a4fa0bad1c0ca31f39e146901d7cd83534bb2c30b83d3470a1e2f3591bd6854539eb2a0d1bd095d2f73695301c8282e739a9ed3c5ad6d991bfb07a0b9e10a381432ecfcb9c6dd0ef9c3c5aa0b540bddd3ffc1b4395fb3e3d564b5d6b70333e6e58e83771add2cd5b07e1efd6ee14f5ea20bf874a63d32accea8b16f3c589d5772061eb528ee379c27afa3f29dcd081dcc38fc3 | xxd -r -p | openssl enc -d -aes-256-ecb | sed 's/SESAME0$//g' | wc -c
>
> 2. Place this encrypted key in a file named "cryptheader." Everything
> should be identical to the following except for the enc_key field,
> which will be the result of step one:
>
> version = '0'
> uuid = '0123-4567-89ab-cdef'
> block_key_cipher = 'aes'
> block_key_sha1 = 'FIXME'
> enc_key_cipher = 'aes-256-ecb'
> enc_key = '53616c7465645f5fbbb762c0ce401215307903ac9c745462e28e37ab1f5721fd68fd0fc40e506e599b779b4ed668cc5df25d4a871d92a61cf2961e43d0532c520bcb203c323355cd55b6717083b8055a10bd682ac08d8ddeb27cf6af927dbfc814d7f133c346deff5235d8e714158d555bbd62df682001cd3611eaedb5d16a562fbc14d6feffad23573bf3af153f166146f9ec7a7fc1a30f07299260652e6eeb'
>
> 3. Create a dm-crypt device for the device you wish to encrypt, using
> the field named enc_key from step two (when prompted, enter the passphrase
> from step one):
>
> echo -n <enc_key> | xxd -r -p | openssl enc -d -aes-256-ecb | sed 's/SESAME0$//g' | cryptsetup -s 128 -c aes create foo <device>
>
> 4. Create an encrypted filesystem on the device:
>
> mkfs.ext2 /dev/mapper/foo
>
> 5. Install the header from step two:
>
> cryptsetup remove foo
> dd if=/dev/zero of=<device> bs=1c count=512
> dd if=cryptheader of=<device> bs=1c count=512
>
> 6. Try sesame-setup:
>
> echo <passphrase from step one> | sesame-setup -v <device>
> mount /dev/mapper/sesame_crypto_0123-4567-89ab-cdef /mnt/foo
>
I think this looks pretty good and complete for sesame-setup apart from
a few items such as maybe a flag to ask for the password on stdin for
use in the initrd?
> Here is the patch:
>
A few comments inline follows
> ===============================================================================
> diff -u --recursive sesame-vanilla/configure.in sesame/configure.in
> --- sesame-vanilla/configure.in 2004-12-17 10:56:52.000000000 -0600
> +++ sesame/configure.in 2004-12-21 20:40:55.427719336 -0600
> @@ -103,6 +103,14 @@
>
> #pkg_modules="hal >= 0.4.0, hal-storage >= 0.4.0, openssl >= 0.9.7a"
> #PKG_CHECK_MODULES(PACKAGE, [$pkg_modules])
> +AC_CHECK_LIB(crypto, EVP_DecryptInit_ex)
> +AC_CHECK_LIB(ssl, SSL_load_error_strings)
> +
> +AC_PATH_PROG(CRYPTSETUP, cryptsetup, no)
> +if test x"$CRYPTSETUP" = xno; then
> + AC_MSG_ERROR([cryptsetup executable not found in your path])
> +fi
> +AC_SUBST(CRYPTSETUP)
This only works for me if I put /sbin in my $PATH - on FC3, that binary
is in /sbin. Any idea what to do here? I think we always want to assume
that cryptsetup is in /sbin so sesame-setup works without /usr mounted,
right?
Btw, is there any good reason for relying on cryptsetup rather than just
dmsetup? Either way is in principle fine with me.
>
> AS_AC_EXPAND(LOCALSTATEDIR, $localstatedir)
> AS_AC_EXPAND(SYSCONFDIR, $sysconfdir)
> Only in sesame: depcomp
> Only in sesame: INSTALL
> Only in sesame: install-sh
> diff -u --recursive sesame-vanilla/tools/Makefile.am sesame/tools/Makefile.am
> --- sesame-vanilla/tools/Makefile.am 2004-12-17 11:02:42.000000000 -0600
> +++ sesame/tools/Makefile.am 2004-12-21 20:41:52.966972040 -0600
> @@ -1,9 +1,9 @@
>
> INCLUDES = \
> + -DCRYPTSETUP=\""$(CRYPTSETUP)\"" \
> -DPACKAGE_DATA_DIR=\""$(datadir)"\" \
> -DPACKAGE_BIN_DIR=\""$(bindir)"\" \
> - -DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \
> - @PACKAGE_CFLAGS@
> + -DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\"
>
> sbin_PROGRAMS = sesame-setup
>
> Only in sesame/tools: Makefile.in
> diff -u --recursive sesame-vanilla/tools/sesame-setup.c sesame/tools/sesame-setup.c
> --- sesame-vanilla/tools/sesame-setup.c 2004-12-17 10:56:52.000000000 -0600
> +++ sesame/tools/sesame-setup.c 2004-12-21 20:58:06.612955352 -0600
> @@ -1,46 +1,406 @@
> -
> #include <stdio.h>
> #include <string.h>
> -#include <sys/types.h>
> -#include <sys/stat.h>
> #include <fcntl.h>
> #include <unistd.h>
> #include <errno.h>
> +#include <ctype.h>
> +#include <assert.h>
> +#include <limits.h>
> +#include <getopt.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <sys/wait.h>
> +#include <openssl/evp.h>
> +#include <openssl/err.h>
> +
> +#ifndef EVP_MAX_BLOCK_LENGTH
> +#define EVP_MAX_BLOCK_LENGTH 32 /* some older openssl versions need this */
> +#endif
>
> #include "../libsesame/libsesame.h"
>
> -int
> -main (int argc, char *argv[])
> +#define MAGICTAIL "SESAME0"
> +
> +int verbose = 0;
> +char *device = NULL;
> +
These two variables should be declared static.
> +static void print_usage(const int exitcode, const char *error,
> + const char *more)
> {
> - char md_file[] = "/dev/sda1";
> - int fd_metadata;
> + if (error)
> + assert(more);
> +
> + fprintf(stderr, "sesame-setup [options] device\n\n"
> + "-h, --help\n"
> + " print a list of options\n\n"
> + "-v, --verbose\n" " verbose display of messages\n\n");
> + if (error)
> + fprintf(stderr, "%s: %s\n", error, more);
> +
> + exit(exitcode);
> +}
> +
> +static void sslerror(const char *msg)
> +{
> + assert(msg);
> +
> + unsigned long err = ERR_get_error();
> + if (err != 0)
> + fprintf(stderr, "%s: %s", msg,
> + ERR_error_string(err, NULL));
> +}
> +
> +static int hash_authtok(const char *data, const EVP_CIPHER * const cipher,
> + const char *const authtok,
> + unsigned char *const hash, unsigned char *const iv)
> +{
> + const EVP_MD *md;
> + unsigned char salt[PKCS5_SALT_LEN];
> + char magic[sizeof "Salted__" - 1];
It appears this variable is not used.
> +
> + assert(data != NULL);
> + assert(cipher != NULL); /* FIXME: is cipher is valid OpenSSL cipher? */
> + assert(authtok != NULL);
> + assert(hash != NULL); /* FIXME: check hash is big enough? */
> + assert(iv != NULL); /* FIXME: check iv is big enough? */
> +
> + if (memcmp(data, "Salted__", sizeof "Salted__" - 1) != 0) {
> + fprintf(stderr, "magic string Salted__ not in stream\n");
> + return 0;
> + }
> + memcpy(salt, data + sizeof "Salted__" - 1, PKCS5_SALT_LEN);
> + md = EVP_md5();
> + if (EVP_BytesToKey
> + (cipher, md, salt, authtok, strlen(authtok), 1,
> + hash, iv) <= 0) {
> + fprintf(stderr, "failed to hash passphrase");
> + return 0;
> + }
> +
> + return 1;
> +}
> +
> +static int
> +decrypt(char *const out, size_t * const out_len,
> + const char *const in, const size_t in_len,
> + const char *const cipher_name, const char *const authtok)
> +{
> + int ret = 1;
> + int segment_len;
> + size_t data_len;
> + char *data;
Can be const char *.
> + unsigned char hashed_authtok[EVP_MAX_KEY_LENGTH];
> + unsigned char iv[EVP_MAX_IV_LENGTH];
> + const EVP_CIPHER *cipher;
> + EVP_CIPHER_CTX ctx;
> +
> + assert(out != NULL);
> + assert(out_len != NULL);
> + assert(cipher_name != NULL);
> + assert(in != NULL);
> + assert(authtok != NULL);
> +
> + memset(out, 0x00, BUFSIZ + EVP_MAX_BLOCK_LENGTH);
> + OpenSSL_add_all_ciphers();
> + EVP_CIPHER_CTX_init(&ctx);
> + SSL_load_error_strings();
You need to include openssl/ssl.h for this one, otherwise the compiler
gives a warning.
> + if (!(cipher = EVP_get_cipherbyname(cipher_name))) {
> + fprintf(stderr, "error getting cipher \"%s\"\n", cipher);
Need to cast cipher to a const char * to avoid a compiler warning -
would be better with a convenience function to get the textual
representation or name of the cipher than this - does this exist?
> + ret = 0;
> + goto _return;
> + }
> + if (hash_authtok(in, cipher, authtok, hashed_authtok, iv) == 0) {
> + ret = 0;
> + goto _return;
> + }
> + if (EVP_DecryptInit_ex(&ctx, cipher, NULL, hashed_authtok, iv) ==
> + 0) {
> + sslerror("failed to initialize decryption code");
> + ret = 0;
> + goto _return;
> + }
> + data = in + (sizeof "Salted__" - 1) + PKCS5_SALT_LEN;
> + data_len = in_len - (sizeof "Salted__" - 1) - PKCS5_SALT_LEN;
> + /* assumes plaintexts is always <= ciphertext + EVP_MAX_BLOCK_LEN in length
> + * OpenSSL's documentation seems to promise this */
> + if (EVP_DecryptUpdate
> + (&ctx, out, &segment_len, data, data_len) == 0) {
> + sslerror("failed to decrypt key");
> + ret = 0;
> + goto _return;
> + }
> + *out_len = segment_len;
> + if (EVP_DecryptFinal_ex(&ctx, &out[*out_len], &segment_len) == 0) {
> + sslerror
> + ("bad pad on end of encrypted file (wrong algorithm or key size?)");
> + ret = 0;
> + goto _return;
> + }
> + *out_len += segment_len;
> + _return:
> + if (EVP_CIPHER_CTX_cleanup(&ctx) == 0) {
> + sslerror("error cleaning up cipher context");
> + ret = 0;
> + }
> +
> + ERR_free_strings();
> + /* out_len is unsigned */
> + assert(ret == 0 || *out_len <= BUFSIZ + EVP_MAX_BLOCK_LENGTH);
> +
> + return ret;
> +}
> +
> +static unsigned char *decode(char *data)
> +{
> + size_t i;
> + unsigned char *decoded =
> + (char *) malloc((strlen(data) / 2) * sizeof(char));
> + if (decoded == NULL)
> + return NULL;
> + for (i = 0; i < strlen(data); i += 2) {
> + decoded[i / 2] =
> + isdigit(data[i]) ? (data[i] - 48) << 4 : (data[i] -
> + 87) << 4;
> + decoded[i / 2] +=
> + isdigit(data[i + 1]) ? data[i + 1] - 48 : data[i + 1] -
> + 87;
> + }
> + return decoded;
> +}
> +
> +static char *strip_cr(char *s)
> +{
> + int len;
> +
> + assert(s);
> +
> + len = strlen(s);
> + s[len - 1] = s[len - 1] == '\n' ? 0x00 : s[len - 1];
> +
> + return s;
> +}
> +
> +static int read_key(char *buf, int size)
> +{
> + int fnval = 1;
> +
> + assert(buf);
> + assert(size > 0);
> +
> + if (fgets(buf, size, stdin) == NULL) {
> + fnval = 0;
> + goto _return;
> + }
> +
> + strip_cr(buf);
> +
> + _return:
> + return fnval;
> +}
> +
> +static void msg(const char *format, ...)
> +{
> + assert(format != NULL);
> +
> + if (verbose) {
> + /* Used to log issues that cause pam_mount to fail. */
> + va_list args;
> +
> + va_start(args, format);
> + vfprintf(stdout, format, args);
> + va_end(args);
> + }
> +}
> +
> +static int run_cryptsetup(const char *block_key_cipher, const char *device,
> + const char *uuid, const char *key,
> + const int key_len)
> +{
> + pid_t child;
> + int fnval = 1, pipefd[2], child_exit;
> + char dmname[PATH_MAX + 1], *key_len_str;
> +
> + assert(block_key_cipher != NULL);
> + assert(device != NULL);
> + assert(uuid != NULL);
> + assert(key != NULL);
> + assert(key_len > 0);
> +
> + if (asprintf(&key_len_str, "%d", key_len) == -1) {
Need to #define _GNU_SOURCE before including stdio.h
> + fprintf(stderr, "Failed to allocate memory, err=%s\n",
> + strerror(errno));
> + fnval = 0;
> + goto _return_no_free;
> + }
> +
> + strcpy(dmname, "sesame_crypto_");
> + strncat(dmname, uuid, sizeof dmname - strlen(dmname));
> +
> + if (pipe(pipefd) == -1) {
> + fprintf(stderr, "Failed to create pipe, err=%s\n",
> + strerror(errno));
> + fnval = 0;
> + goto _return;
> + }
> +
> + child = fork();
> +
> + if (child < 0) {
> + fprintf(stderr, "Failed to fork, err=%s\n",
> + strerror(errno));
> + fnval = 0;
> + goto _return;
> + } else if (child == 0) {
> + close(0);
> + dup(pipefd[0]);
> + close(pipefd[0]);
> + close(pipefd[1]);
> + execl(CRYPTSETUP, "cryptsetup", "-s", key_len_str, "-c",
> + block_key_cipher, "create", dmname, device, NULL);
> + fprintf(stderr, "Failed to execute %s, err=%s\n",
> + CRYPTSETUP, strerror(errno));
> + exit(EXIT_FAILURE);
> + } else {
> + close(pipefd[0]);
> + write(pipefd[1], key, key_len);
> + close(pipefd[1]);
> + waitpid(child, &child_exit, 0);
> + fnval = !WEXITSTATUS(child_exit);
> + goto _return;
> + }
> +
> + _return:
> + free(key_len_str);
> + _return_no_free:
> + return fnval;
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + int c, opt_index = 0;
> + char passphrase[BUFSIZ + 1];
> + char dec_key[BUFSIZ + EVP_MAX_KEY_LENGTH];
> + int dec_key_len, real_dec_key_len;
> char buf[1024];
> + int fd_metadata;
> + char *uuid, *enc_key, *enc_key_cipher, *block_key_cipher,
> + *block_key_sha1;
Needs to be const char *.
> + struct option opts[] = {
> + {"help", 0, 0, 'h'},
> + {"verbose", 0, 0, 'v'},
> + {0, 0, 0, 0}
> + };
> +
> + while ((c = getopt_long(argc, argv, "hv", opts, &opt_index)) >= 0) {
> + switch (c) {
> + case 'h':
> + print_usage(EXIT_SUCCESS, NULL, NULL);
> + case 'v':
> + verbose = 1;
> + break;
> + default:
> + print_usage(EXIT_FAILURE, NULL, NULL);
> + }
> + }
> +
> + if (argv[optind] == NULL)
> + print_usage(EXIT_FAILURE, NULL, NULL);
> + device = argv[optind];
> +
> SesameMetaData *md;
Moved this to the top of the function - all variables should be declared
in the top of the function.
>
> - fd_metadata = open (md_file, O_RDONLY);
> + msg("opening %s\n", device);
> + fd_metadata = open(device, O_RDONLY);
> if (fd_metadata == -1) {
> - printf ("Cannot open %s, err=%s\n", md_file, strerror (errno));
> + fprintf(stderr, "Cannot open %s, err=%s\n", device,
> + strerror(errno));
> goto error;
> }
> - if (read (fd_metadata, buf, sizeof (buf)) == -1) {
> - printf ("Cannot read from %s, err=%s\n",
> - md_file, strerror (errno));
> +
> + msg("reading from %s\n", device);
> + if (read(fd_metadata, buf, sizeof(buf)) == -1) {
> + fprintf(stderr, "Cannot read from %s, err=%s\n",
> + device, strerror(errno));
> goto error1;
> }
>
> - md = sesame_get_metadata_from_buf (buf);
> + msg("parsing metadata\n");
> + md = sesame_get_metadata_from_buf(buf);
> if (md == NULL) {
> - printf ("Cannot not parse metadata\n");
> + fprintf(stderr, "Cannot not parse metadata\n");
> + goto error1;
> + }
> +
> + msg("getting uuid\n");
> + uuid = sesame_get(md, "uuid");
> + if (uuid == NULL) {
> + fprintf(stderr, "Cannot read uuid from %s\n", device);
> + goto error1;
Here you are leaking the SesameMetadata object; need to goto error2
instead - see below
> + }
> +
> + msg("getting enc_key\n");
> + enc_key = sesame_get(md, "enc_key");
> + if (enc_key == NULL) {
> + fprintf(stderr, "Cannot read enc_key from %s\n", device);
> goto error1;
error2
> }
>
> - printf ("uuid = %s\n", sesame_get (md, "uuid"));
> - printf ("enc_key = %s\n", sesame_get (md, "enc_key"));
> + msg("getting enc_key_cipher\n");
> + enc_key_cipher = sesame_get(md, "enc_key_cipher");
> + if (enc_key == NULL) {
> + fprintf(stderr, "Cannot read enc_key_cipher from %s\n",
> + device);
> + goto error1;
> + }
error2
> +
> + msg("getting block_key_cipher\n");
> + block_key_cipher = sesame_get(md, "block_key_cipher");
> + if (block_key_cipher == NULL) {
> + fprintf(stderr, "Cannot read block_key_cipher from %s\n",
> + device);
> + goto error1;
> + }
error2
> +
> + msg("getting block_key_sha1\n");
> + block_key_sha1 = sesame_get(md, "block_key_sha1");
> + if (block_key_sha1 == NULL) {
> + fprintf(stderr, "Cannot read block_key_sha1 from %s\n",
> + device);
> + goto error1;
> + }
error2
> +
> + if (read_key(passphrase, BUFSIZ) == 0) {
> + fprintf(stderr, "Could not read key\n");
> + goto error1;
> + }
error2
> +
> + msg("decrypting key using passphrase\n");
> + if (decrypt
> + (dec_key, &dec_key_len, decode(enc_key), strlen(enc_key) / 2,
You are leaking the memory allocated by decode(enc_key) here - also, the
types are mixed up.
> + enc_key_cipher, passphrase) == 0) {
> + fprintf(stderr, "Cannot decrypt key\n");
> + goto error1;
> + }
error2
> +
> + msg("checking for MAGICTAIL tail in key\n");
> + real_dec_key_len = dec_key_len - strlen(MAGICTAIL);
> + if (memcmp
> + (dec_key + real_dec_key_len, MAGICTAIL, strlen(MAGICTAIL))) {
> + fprintf(stderr, "Key does not end in %s\n", MAGICTAIL);
> + goto error1;
> + }
> +
> + msg("executing cryptsetup\n");
> + if (run_cryptsetup(block_key_cipher, device, uuid, dec_key,
> + real_dec_key_len) == 0) {
> + goto error1;
> + }
error2
>
> - sesame_free (md);
Need to have error2: label here.
> + msg("freeing memory\n");
> + sesame_free(md);
>
> -error1:
> - close (fd_metadata);
> -error:
> + error1:
> + close(fd_metadata);
> + error:
> return 0;
> }
> ===============================================================================
>
I've committed your patch along with the changes notes above and I've
also added a ChangeLog entry - for future commits please add an entry
detailing the changes.
Thanks,
David
_______________________________________________
hal mailing list
hal at lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/hal
More information about the Hal
mailing list