Skip site navigation (1)Skip section navigation (2)
Date:      25 Sep 2002 17:22:04 -0600
From:      James Gritton <gritton@iserver.com>
To:        hackers@FreeBSD.ORG
Subject:   The poor man's cryptfs
Message-ID:  <x7r8fhk63n.fsf@guppy.dmz.orem.verio.net>

next in thread | raw e-mail | index | archive | help
After playing with a few encrypted filesystems, and giving up on them (after
a kernel crash or two), I went looking for something else to encrypt.  The
logical choice is the device.

Well, the virtual device.  Like a cryptfs that's based on a loopback mount,
I'm encrypting a virtual device based on the "vn" driver.  This was only a
few hours' work, though it's admittedly incomplete.  This is based on the
Blowfish code in the kernel used by ipsec and such, which an extra ioctl
added to set the key.  Only three source files require modification:

sys/sys/vnioctl.h:
  Define the VNIOCSETKEY ioctl

usr.sbin/vnconfig/vnconfig.c:
  Add a "-k" option to specify that an encryption key should be entered via
  getpass(), and passed in with the above ioctl.

sys/dev/vn/vn.c
  Add a blowfish key entry to the softc structure.  This is set via the
  above ioctl, which converts a passed-in string into the key data.
  Encryption is done around the vn's VOP_READ and VOP_WRITE calls, in
  512-byte CBC chunks.

That's it - 90 lines of new code.  This is for my purposes complete and
working, which is to say neither is quite true.  For production-quality
code, some work remains:

* The vn.c code is tied to blowfish.  It would be better to have some
  dynamically selectable encryption scheme.  I like blowfish and it's
  available, but others may want something else.  The vnconfig hack doesn't
  contain any mention of an encryption scheme - it just passes a key string.

* It doesn't work with swap-backed vn devices.  It wouldn't be hard to put
  the same encrypt-decrypt wrappers around the vm_pager_strategy() call, or
  perhaps even to merely move the wrappers further out to encompass both
  vnode and object access methods.

* It doesn't work with labels, probably because of some kernel function(s)
  that make their own read/write calls outside of the vnstrategy's own
  VOP_READ and VOP_WRITE.  To build a new filesystem, I had to newfs a
  swap-backed vn and dd it to the encrypted vn.  But then I can mount the
  encrypted vn without worrying about labels.

* It requires the blowfish functions to be linked into the kernel, which I
  accomplished by changing them from "optional ... ipsec" to "standard" in
  the kernel.  I suppose I could have somehoe linked them to vn.ko directly,
  but that seems the wrong way around.  So even though vn is a module, it
  requires a kernel recompile to use it, unless you're running ipsec or
  ipv6.

The diffs for what I have are small.  If someone wants to make it (more)
complete, the further diffs should be small as well.

- Jamie





--- sys/sys/vnioctl.h.orig	Wed Sep 25 16:38:53 2002
+++ sys/sys/vnioctl.h	Tue Sep 24 22:17:26 2002
@@ -68,6 +68,9 @@
 #define VNIOCGCLEAR	_IOWR('F', 3, u_long )		/* reset --//-- */
 #define VNIOCUSET	_IOWR('F', 4, u_long )		/* set unit option */
 #define VNIOCUCLEAR	_IOWR('F', 5, u_long )		/* reset --//-- */
+#ifdef VNCRYPT
+#define VNIOCSETKEY	_IOW('F', 47, char * )		/* set key */
+#endif
 
 #define VN_LABELS	0x1	/* Use disk(/slice) labels */
 #define VN_FOLLOW	0x2	/* Debug flow in vn driver */
--- usr.sbin/vnconfig/vnconfig.c.orig	Wed Sep 25 16:38:38 2002
+++ usr.sbin/vnconfig/vnconfig.c	Wed Sep 25 16:43:44 2002
@@ -88,6 +88,9 @@
 #define VN_RESET	0x200
 #define VN_TRUNCATE	0x400
 #define VN_ZERO		0x800
+#ifdef VNCRYPT
+#define VN_SETKEY	0x1000
+#endif
 
 int nvndisks;
 
@@ -118,7 +121,11 @@
 	char *s;
 
 	configfile = _PATH_VNTAB;
+#ifdef VNCRYPT
+	while ((i = getopt(argc, argv, "acdef:gkr:s:S:TZL:uv")) != -1)
+#else
 	while ((i = getopt(argc, argv, "acdef:gr:s:S:TZL:uv")) != -1)
+#endif
 		switch (i) {
 
 		/* all -- use config file */
@@ -154,6 +161,13 @@
 			global = 1 - global;
 			break;
 
+#ifdef VNCRYPT
+		/* set key */
+		case 'k':
+			flags |= VN_SETKEY;
+			break;
+#endif
+
 		/* reset options */
 		case 'r':
 			for (s = strtok(optarg, ","); s; s = strtok(NULL, ",")) {
@@ -399,6 +413,18 @@
 				    dev, vnio.vn_size, file
 				);
 			}
+#ifdef VNCRYPT
+			if (flags & VN_SETKEY) {
+				char *key;
+
+				/* Read an encryption key and set it */
+				key = getpass("key: ");
+				if (!key[0])
+					key = NULL;
+				if (ioctl(fileno(f), VNIOCSETKEY, &key))
+					warn("VNIOCSETKEY");
+			}
+#endif
 			/*
 			 * autolabel
 			 */
--- sys/dev/vn/vn.c.orig	Wed Sep 25 16:39:19 2002
+++ sys/dev/vn/vn.c	Wed Sep 25 16:43:24 2002
@@ -87,6 +87,10 @@
 #include <vm/vm_extern.h>
 #include <vm/vm_zone.h>
 
+#ifdef VNCRYPT
+#include <crypto/blowfish/blowfish.h>
+#endif
+
 static	d_ioctl_t	vnioctl;
 static	d_open_t	vnopen;
 static	d_close_t	vnclose;
@@ -140,6 +144,9 @@
 	struct buf	 sc_tab;	/* transfer queue 		*/
 	u_long		 sc_options;	/* options 			*/
 	dev_t		 sc_devlist;	/* devices that refer to this unit */
+#ifdef VNCRYPT
+	BF_KEY		 sc_key;	/* blowfish key			*/
+#endif
 	SLIST_ENTRY(vn_softc) sc_list;
 };
 
@@ -385,12 +392,53 @@
 			auio.uio_rw = UIO_WRITE;
 		auio.uio_resid = bp->b_bcount;
 		auio.uio_procp = curproc;
+#ifdef VNCRYPT
+		if (vn->sc_key.P[0] && !(bp->b_flags & B_READ)) {
+			BF_LONG *blp;
+
+			/* Encode data to be written using CBC.
+			 * The chain size is the disk sector size - 512 bytes.
+			 */
+			for (blp = (BF_LONG *)bp->b_data; blp <
+			     (BF_LONG *)((char *)bp->b_data + bp->b_bcount);
+			     blp += 2) {
+				if (((char *)blp - (char *)bp->b_bcount) &
+				    0x1ff)
+					blp[0] ^= blp[-2], blp[1] ^= blp[-1];
+				BF_encrypt(blp, &vn->sc_key);
+			}
+		}
+#endif
 		vn_lock(vn->sc_vp, LK_EXCLUSIVE | LK_RETRY, curproc);
 		if (bp->b_flags & B_READ)
 			error = VOP_READ(vn->sc_vp, &auio, IO_DIRECT, vn->sc_cred);
 		else
 			error = VOP_WRITE(vn->sc_vp, &auio, IO_NOWDRAIN, vn->sc_cred);
 		VOP_UNLOCK(vn->sc_vp, 0, curproc);
+#ifdef VNCRYPT
+		if (vn->sc_key.P[0]) {
+			int len;
+			BF_LONG *blp;
+			BF_LONG blx[2], bly[2];
+
+			/* Decode data using CBC.  This is for both reads
+			 * (which are encoded in the vnode) and writes
+			 * (which I just encoded before writing).
+			 */
+			len = (bp->b_flags & B_READ)
+			    ? bp->b_bcount - auio.uio_resid : bp->b_bcount;
+			for (blp = (BF_LONG *)bp->b_data;
+			     blp < (BF_LONG *)((char *)bp->b_data + len);
+			     blp += 2) {
+				blx[0] = blp[0], blx[1] = blp[1];
+				BF_decrypt(blp, &vn->sc_key);
+				if (((char *)blp - (char *)bp->b_bcount) &
+				    0x1ff)
+					blp[0] ^= bly[0], blp[1] ^= bly[1];
+				bcopy(blx, bly, sizeof blx);
+			}
+		}
+#endif
 		bp->b_resid = auio.uio_resid;
 
 		if (error) {
@@ -443,6 +491,9 @@
 	case VNIOCGCLEAR:
 	case VNIOCUSET:
 	case VNIOCUCLEAR:
+#ifdef VNCRYPT
+	case VNIOCSETKEY:
+#endif
 		goto vn_specific;
 	}
 
@@ -513,6 +564,42 @@
 		*f = vn->sc_options;
 		break;
 
+#ifdef VNCRYPT
+	case VNIOCSETKEY:
+	    /* Set the blowfish key */
+	    {
+		int i, j;
+		unsigned char *kc, *kp;
+		unsigned char kbuf[(BF_ROUNDS + 2) * 4];
+
+		if (!*(char **)data) {
+			vn->sc_key.P[0] = 0;
+			break;
+		}
+		kp = zalloc(namei_zone);
+		/* Read in an ASCII string and convert it into binary form.
+		 * This may not be a "standard" way to convert it, but I
+		 * think it allows for good bit coverage in long strings.
+		 */
+		if ((error = copyinstr(*(char **)data, kp, MAXPATHLEN, NULL))) {
+			zfree(namei_zone, kp);
+			return error;
+		}
+		bzero(kbuf, sizeof kbuf);
+		for (kc = kp, i = j = 0; *kc; kc++, i++) {
+			if (i == sizeof kbuf) {
+				i = 0;
+				j = (j + 5) & 7;
+			}
+			kbuf[i] ^= (*kc >> j) | (*kc << (8 - j));
+		}
+		zfree(namei_zone, kp);
+		/* Now convert that binary key into Blowfish's internal form */
+		BF_set_key(&vn->sc_key, sizeof kbuf, kbuf);
+		break;
+	    }
+#endif
+
 	default:
 		error = ENOTTY;
 		break;
@@ -740,6 +827,9 @@
 		vn->sc_object = NULL;
 	}
 	vn->sc_size = 0;
+#ifdef VNCRYPT
+	vn->sc_key.P[0] = 0;
+#endif
 }
 
 static	int
--- sys/conf/files.orig	Wed Sep 25 16:37:47 2002
+++ sys/conf/files	Tue Sep 24 23:16:16 2002
@@ -80,7 +80,7 @@
 contrib/ipfilter/netinet/ip_proxy.c	optional ipfilter inet
 contrib/ipfilter/netinet/ip_state.c	optional ipfilter inet
 contrib/ipfilter/netinet/mlfk_ipl.c	optional ipfilter inet
-crypto/blowfish/bf_skey.c	optional ipsec ipsec_esp
+crypto/blowfish/bf_skey.c	standard
 crypto/cast128/cast128.c	optional ipsec ipsec_esp
 crypto/des/des_ecb.c	optional ipsec ipsec_esp
 crypto/des/des_setkey.c	optional ipsec ipsec_esp
--- sys/conf/files.i386.orig	Wed Sep 25 16:37:59 2002
+++ sys/conf/files.i386	Tue Sep 24 23:16:32 2002
@@ -73,7 +73,7 @@
 contrib/dev/oltr/trlldbm.c	optional	oltr
 contrib/dev/oltr/trlldhm.c	optional	oltr
 contrib/dev/oltr/trlldmac.c	optional	oltr
-bf_enc.o			optional	ipsec ipsec_esp		\
+bf_enc.o			standard				\
 	dependency	"$S/crypto/blowfish/arch/i386/bf_enc.S $S/crypto/blowfish/arch/i386/bf_enc_586.S $S/crypto/blowfish/arch/i386/bf_enc_686.S"		\
 	compile-with	"${CC} -c -I$S/crypto/blowfish/arch/i386 ${ASM_CFLAGS} ${WERROR} ${.IMPSRC}"	\
 	no-implicit-rule

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-hackers" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?x7r8fhk63n.fsf>