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