[PATCH] Network passphrase reading

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I have a machine with a rootfs on dm-crypt, so the initramfs runs
cryptsetup to unlock it at boot time.  A couple of weeks ago, I remote
booted it (wake-on-LAN magic packet), and afterward realized that the
reason it wasn't showing up on the network was that it was waiting on
the passphrase.  :-)

So with a couple of changes to the initramfs, and the attached patch
(against current SVN), I could send the passphrase over the network
instead of typing it in.

It's cleartext, which is crappy, but pulling in OpenSSL and requiring
certs would be hard, it's disabled by default, and I trust my local
network.  If I didn't care about doing both network and terminal, I'd
just "openssl s_server -quiet" and pipe the result into cryptsetup.  But
most of the time I'm sitting at the keyboard; I only need the network
support when I'm trying to look at something remotely and the machine is
off.

...Although after writing this patch, it occurs to me that it might be
better to add a general --secondary-fd=X argument (with X any number),
which would make cryptsetup read from X as well, in the select loop.
That would get rid of some of the network eccentricities in the loop,
though making it general-purpose enough might be hard.  It would also
remove the ability to preempt an existing connection (and throw away any
data it has sent).

Comments?  Would people prefer the secondary-FD approach instead?
Index: src/cryptsetup.c
===================================================================
--- src/cryptsetup.c	(revision 188)
+++ src/cryptsetup.c	(working copy)
@@ -37,6 +37,8 @@
 static int opt_tries = 3;
 static int opt_align_payload = 0;
 static int opt_non_exclusive = 0;
+static int opt_network_password = 0;
+static int opt_network_port = 4994;
 
 static const char **action_argv;
 static int action_argc;
@@ -412,6 +414,7 @@
 		.key_size = opt_key_file ? (opt_key_size / 8) : 0, /* limit bytes read from keyfile */
 		.timeout = opt_timeout,
 		.tries = opt_key_file ? 1 : opt_tries, /* verify is usefull only for tty */
+		.network_port = opt_network_password ? opt_network_port : 0,
 		.icb = &cmd_icb,
 	};
 
@@ -561,6 +564,9 @@
 	if ((r = crypt_load(cd, CRYPT_LUKS1, NULL)))
 		goto out;
 
+	if ((r = crypt_set_network_port(cd, opt_network_password ? opt_network_port : 0)))
+		goto out;
+
 	if (opt_key_file)
 		r = crypt_resume_by_keyfile(cd, action_argv[0], CRYPT_ANY_SLOT,
 					    opt_key_file, opt_key_size / 8);
@@ -721,6 +727,8 @@
 		{ "align-payload",     '\0', POPT_ARG_INT, &opt_align_payload,          0, N_("Align payload at <n> sector boundaries - for luksFormat"), N_("SECTORS") },
 		{ "non-exclusive",     '\0', POPT_ARG_NONE, &opt_non_exclusive,         0, N_("(Obsoleted, see man page.)"), NULL },
 		{ "header-backup-file",'\0', POPT_ARG_STRING, &opt_header_backup_file,  0, N_("File with LUKS header and keyslots backup."), NULL },
+		{ "network-password",  '\0', POPT_ARG_NONE, &opt_network_password,      0, N_("Whether to accept a passphrase over the network"), NULL },
+		{ "password-port",     '\0', POPT_ARG_INT, &opt_network_port,           0, N_("TCP port to listen on for a passphrase"), N_("PORT") },
 		POPT_TABLEEND
 	};
 	poptContext popt_context;
Index: lib/libcryptsetup.h
===================================================================
--- lib/libcryptsetup.h	(revision 188)
+++ lib/libcryptsetup.h	(working copy)
@@ -102,11 +102,13 @@
  * @password_retry - number of tries for password if not verified
  * @iteration_time - iteration time for LUKS header in miliseconds
  * @password_verify - for compiled-in password query always verify passwords twice
+ * @network_port - for compiled-in password query, port to listen on for network password, or -1 to disable
  */
 void crypt_set_timeout(struct crypt_device *cd, uint64_t timeout_sec);
 void crypt_set_password_retry(struct crypt_device *cd, int tries);
 void crypt_set_iterarion_time(struct crypt_device *cd, uint64_t iteration_time_ms);
 void crypt_set_password_verify(struct crypt_device *cd, int password_verify);
+void crypt_set_network_port(struct crypt_device *cd, int network_port);
 
 /**
  * Helper to lock/unlock memory to avoid swap sensitive data to disk
@@ -552,6 +554,8 @@
 	uint64_t	align_payload;
 	int             tries;
 
+	int             network_port;
+
 	struct interface_callbacks *icb;
 };
 
Index: lib/utils.c
===================================================================
--- lib/utils.c	(revision 188)
+++ lib/utils.c	(working copy)
@@ -14,6 +14,9 @@
 #include <termios.h>
 #include <sys/mman.h>
 #include <sys/resource.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
 
 #include "libcryptsetup.h"
 #include "internal.h"
@@ -304,42 +307,215 @@
 	return write_blockwise(fd, buf, count) + innerCount;
 }
 
+/* Networking helper */
+
+static int setup_socket(struct crypt_device *cd, int network_port)
+{
+	int sockfd;
+	struct sockaddr_in addr;
+	int one = 1;
+
+	if(network_port <= 0)
+		return -1;
+
+	sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+	if(sockfd < 0) {
+		log_std(cd, _("socket() failed: skipping network.\n"));
+		return -1;
+	} else {
+		log_dbg(_("socket() succeeded."));
+	}
+
+	if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
+		log_std(cd, _("setsockopt(SO_REUSEADDR) failed; bind may fail next.\n"));
+	} else {
+		log_dbg(_("setsockopt(SO_REUSEADDR) succeeded."));
+	}
+
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons((uint16_t)network_port);
+	addr.sin_addr.s_addr = INADDR_ANY;
+
+	if(bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+		log_std(cd, _("bind() failed: skipping network.\n"));
+		close(sockfd);
+		return -1;
+	} else {
+		log_dbg(_("bind() succeeded."));
+	}
+
+	if(listen(sockfd, 1) < 0) {
+		log_std(cd, _("listen() failed: skipping network.\n"));
+		close(sockfd);
+		return -1;
+	} else {
+		log_dbg(_("listen() succeeded, socket OK."));
+	}
+
+	return sockfd;
+}
+
 /* Password reading helpers */
 
-static int untimed_read(int fd, char *pass, size_t maxlen)
+static int single_read(int fd, int kill_newline, char *pass, size_t maxlen)
 {
 	ssize_t i;
 
 	i = read(fd, pass, maxlen);
 	if (i > 0) {
-		pass[i-1] = '\0';
-		i = 0;
+		if (kill_newline)
+			pass[i-1] = '\0';
 	} else if (i == 0) { /* EOF */
 		*pass = 0;
-		i = -1;
 	}
 	return i;
 }
 
-static int timed_read(int fd, char *pass, size_t maxlen, long timeout)
+static int multi_read(int fd, int sockfd, char *pass, size_t maxlen,
+		long timeout, struct crypt_device *cd)
 {
 	struct timeval t;
 	fd_set fds;
 	int failed = -1;
+	int maxfd, clientfd = -1;
+	int rv;
+	char *netbuf = NULL, *ptr;
+	size_t curmaxlen;
 
-	FD_ZERO(&fds);
-	FD_SET(fd, &fds);
-	t.tv_sec = timeout;
-	t.tv_usec = 0;
+	/* Luckily, Linux modifies the struct timeval in select()... */
+	if (timeout) {
+		t.tv_sec = timeout;
+		t.tv_usec = 0;
+	}
 
-	if (select(fd+1, &fds, NULL, NULL, &t) > 0)
-		failed = untimed_read(fd, pass, maxlen);
+	if (sockfd >= 0) {
+		netbuf = malloc(maxlen);
+		if (!netbuf) {
+			log_err(cd, _("Out of memory.\n"));
+			return -1;
+		}
+	}
 
-	return failed;
+	while(1) {
+		FD_ZERO(&fds);
+		FD_SET(fd, &fds);
+		maxfd = fd;
+
+		if (sockfd >= 0) {
+			FD_SET(sockfd, &fds);
+
+			if (sockfd > maxfd)
+				maxfd = sockfd;
+		}
+
+		if (clientfd >= 0) {
+			FD_SET(clientfd, &fds);
+
+			if (clientfd > maxfd)
+				maxfd = clientfd;
+		}
+
+		rv = select(maxfd+1, &fds, NULL, NULL, timeout ? &t : NULL);
+	
+		if (rv < 0 && errno == EINTR)
+			continue;
+
+		if (rv < 0) {
+			log_err(cd, _("select() failed.\n"));
+			failed = -1;
+			break;
+		}
+
+		if (rv == 0) {
+			/* timeout */
+			failed = -1;
+			break;
+		}
+
+		/* terminal input preempts network input */
+		if (FD_ISSET(fd, &fds)) {
+			failed = single_read(fd, 1, pass, maxlen);
+			if (failed == 0)  /* EOF */
+				failed = -1;
+			break;
+		}
+
+		/* check the client first since the sockfd check might reset this FD */
+		if (clientfd >= 0 && FD_ISSET(clientfd, &fds)) {
+			/* Don't kill the newline in this call.  Network passwords
+			 * might come in over multiple read() calls, since TCP is a
+			 * stream, with no defined packet boundaries. */
+			rv = single_read(clientfd, 0, ptr, curmaxlen);
+
+			if (rv < 0) {
+				failed = -1;
+			} else if (rv == 0) {
+				/* client closed connection, no \n received: ignore */
+				shutdown(clientfd, SHUT_RDWR);
+				close(clientfd);
+				clientfd = -1;
+
+				if (!failed)
+					break;
+			} else {
+				char *tmp = memchr(ptr, '\n', curmaxlen);
+
+				if (tmp) {
+					/* success */
+
+					*tmp = '\0';
+					/* telnet sends \r\n, not just \n: kill the \r too */
+					if (tmp > netbuf && *(tmp-1) == '\r')
+						*(tmp-1) = '\0';
+					failed = 0;
+
+					memcpy(pass, netbuf, maxlen);
+					memset(netbuf, 0, maxlen);
+
+					shutdown(clientfd, SHUT_RDWR);
+
+					/* but keep going, so we read to EOF.  setting failed = 0
+					 * above makes the EOF handler exit. */
+				} else if (curmaxlen - rv == 0) {
+					/* end of buffer hit, no \n received: ignore */
+					shutdown(clientfd, SHUT_RDWR);
+					close(clientfd);
+					clientfd = -1;
+				} else {
+					/* keep reading... */
+					ptr += rv;
+					curmaxlen -= rv;
+				}
+			}
+		}
+
+		if (sockfd >= 0 && FD_ISSET(sockfd, &fds)) {
+			/* new connections kill older ones */
+			if (clientfd >= 0) {
+				shutdown(clientfd, SHUT_RDWR);
+				close(clientfd);
+			}
+
+			clientfd = accept(sockfd, NULL, NULL);
+			ptr = netbuf;
+			curmaxlen = maxlen;
+		}
+	}
+
+	if (clientfd >= 0) {
+		shutdown(clientfd, SHUT_RDWR);
+		close(clientfd);
+	}
+
+	if (netbuf)
+		free(netbuf);
+
+	return failed < 0 ? -1 : 0;
 }
 
 static int interactive_pass(const char *prompt, char *pass, size_t maxlen,
-		long timeout)
+		long timeout, int sockfd, struct crypt_device *cd)
 {
 	struct termios orig, tmp;
 	int failed = -1;
@@ -364,10 +540,7 @@
 		goto out_err;
 
 	tcsetattr(infd, TCSAFLUSH, &tmp);
-	if (timeout)
-		failed = timed_read(infd, pass, maxlen, timeout);
-	else
-		failed = untimed_read(infd, pass, maxlen);
+	failed = multi_read(infd, sockfd, pass, maxlen, timeout, cd);
 	tcsetattr(infd, TCSAFLUSH, &orig);
 
 out_err:
@@ -393,10 +566,11 @@
  */
 
 void get_key(char *prompt, char **key, unsigned int *passLen, int key_size,
-            const char *key_file, int timeout, int how2verify,
+            const char *key_file, int timeout, int how2verify, int network_port,
 	    struct crypt_device *cd)
 {
 	int fd = -1;
+	int sockfd = -1;
 	const int verify = how2verify & CRYPT_FLAG_VERIFY;
 	const int verify_if_possible = how2verify & CRYPT_FLAG_VERIFY_IF_POSSIBLE;
 	char *pass = NULL;
@@ -425,6 +599,8 @@
 		fd = STDIN_FILENO;
 		newline_stop = 1;
 		read_horizon = 0;   /* Infinite, if read from terminal or fd */
+
+		sockfd = setup_socket(cd, network_port);
 	}
 
 	/* Interactive case */
@@ -432,13 +608,13 @@
 		int i;
 
 		pass = safe_alloc(MAX_TTY_PASSWORD_LEN);
-		if (!pass || (i = interactive_pass(prompt, pass, MAX_TTY_PASSWORD_LEN, timeout))) {
+		if (!pass || (i = interactive_pass(prompt, pass, MAX_TTY_PASSWORD_LEN, timeout, sockfd, cd))) {
 			log_err(cd, _("Error reading passphrase from terminal.\n"));
 			goto out_err;
 		}
 		if (verify || verify_if_possible) {
 			char pass_verify[MAX_TTY_PASSWORD_LEN];
-			i = interactive_pass(_("Verify passphrase: "), pass_verify, sizeof(pass_verify), timeout);
+			i = interactive_pass(_("Verify passphrase: "), pass_verify, sizeof(pass_verify), timeout, -1, cd);
 			if (i || strcmp(pass, pass_verify) != 0) {
 				log_err(cd, _("Passphrases do not match.\n"));
 				goto out_err;
@@ -509,11 +685,15 @@
 	}
 	if(fd != STDIN_FILENO)
 		close(fd);
+	if(sockfd >= 0)
+		close(sockfd);
 	return;
 
 out_err:
 	if(fd >= 0 && fd != STDIN_FILENO)
 		close(fd);
+	if(sockfd >= 0)
+		close(sockfd);
 	if(pass)
 		safe_free(pass);
 	*key = NULL;
Index: lib/setup.c
===================================================================
--- lib/setup.c	(revision 188)
+++ lib/setup.c	(working copy)
@@ -18,6 +18,7 @@
 	uint64_t iteration_time;
 	int tries;
 	int password_verify;
+	int network_port;
 
 	/* used in CRYPT_LUKS1 */
 	struct luks_phdr hdr;
@@ -199,7 +200,7 @@
 	unsigned int passwordLen;
 
 	get_key(_("Enter any remaining LUKS passphrase: "), &password,
-		&passwordLen, 0, key_file, cd->timeout, flags, cd);
+		&passwordLen, 0, key_file, cd->timeout, flags, 0, cd);
 	if(!password)
 		return -EINVAL;
 
@@ -234,7 +235,7 @@
 	int keyIndex;
 
 	get_key(message,&password,&passwordLen, 0, key_file,
-		cd->timeout, flags, cd);
+		cd->timeout, flags, 0, cd);
 	if(!password)
 		return -EINVAL;
 
@@ -457,7 +458,7 @@
 	} else {
 		if (force_verify || cd->password_verify)
 			flags |= CRYPT_FLAG_VERIFY_IF_POSSIBLE;
-		get_key(msg, key, key_len, 0, NULL, cd->timeout, flags, cd);
+		get_key(msg, key, key_len, 0, NULL, cd->timeout, flags, cd->network_port, cd);
 	}
 }
 
@@ -504,7 +505,7 @@
 			  char **key, unsigned int *key_len,
 			  const char *key_file, size_t key_size)
 {
-	get_key(msg, key, key_len, key_size, key_file, cd->timeout, 0, cd);
+	get_key(msg, key, key_len, key_size, key_file, cd->timeout, 0, 0, cd);
 }
 
 static int _crypt_init(struct crypt_device **cd,
@@ -539,6 +540,7 @@
 	crypt_set_password_retry(*cd, options->tries);
 	crypt_set_iterarion_time(*cd, options->iteration_time ?: 1000);
 	crypt_set_password_verify(*cd, options->flags & CRYPT_FLAG_VERIFY);
+	crypt_set_network_port(*cd, options->network_port);
 
 	if (load && !init_by_name)
 		r = crypt_load(*cd, type, NULL);
@@ -598,7 +600,7 @@
 		return r;
 
 	get_key(_("Enter passphrase: "), &key, &keyLen, options->key_size,
-		options->key_file, cd->timeout, options->flags, cd);
+		options->key_file, cd->timeout, options->flags, 0, cd);
 	if (!key)
 		r = -ENOENT;
 	else
@@ -626,7 +628,7 @@
 		return r;
 
 	get_key(_("Enter passphrase: "), &key, &keyLen, options->key_size,
-		options->key_file, cd->timeout, options->flags, cd);
+		options->key_file, cd->timeout, options->flags, 0, cd);
 	if (!key)
 		r = -ENOENT;
 	else
@@ -782,7 +784,7 @@
 	}
 
 	get_key(_("Enter LUKS passphrase: "), &password, &passwordLen, 0,
-		options->new_key_file, options->timeout, options->flags, cd);
+		options->new_key_file, options->timeout, options->flags, 0, cd);
 
 	if(!password) {
 		r = -EINVAL;
@@ -803,7 +805,7 @@
 	return (r < 0) ? r : 0;
 }
 
-/* OPTIONS: name, device, key_size, key_file, timeout, tries, flags, icb */
+/* OPTIONS: name, device, key_size, key_file, timeout, tries, flags, network_port, icb */
 int crypt_luksOpen(struct crypt_options *options)
 {
 	struct crypt_device *cd = NULL;
@@ -1918,6 +1920,12 @@
 	cd->password_verify = password_verify ? 1 : 0;
 }
 
+void crypt_set_network_port(struct crypt_device *cd, int network_port)
+{
+	log_dbg("Network port set to %d.", network_port);
+	cd->network_port = network_port;
+}
+
 int crypt_memory_lock(struct crypt_device *cd, int lock)
 {
 	return lock ? crypt_memlock_inc(cd) : crypt_memlock_dec(cd);
Index: lib/internal.h
===================================================================
--- lib/internal.h	(revision 188)
+++ lib/internal.h	(working copy)
@@ -102,7 +102,7 @@
 int wipe_device_header(const char *device, int sectors);
 
 void get_key(char *prompt, char **key, unsigned int *passLen, int key_size,
-	     const char *key_file, int timeout, int how2verify,
+	     const char *key_file, int timeout, int how2verify, int network_port,
 	     struct crypt_device *cd);
 
 int parse_into_name_and_mode(const char *nameAndMode, char *name, char *mode);
Index: man/cryptsetup.8
===================================================================
--- man/cryptsetup.8	(revision 188)
+++ man/cryptsetup.8	(working copy)
@@ -46,7 +46,7 @@
 \fIluksOpen\fR <device> <name>
 .IP
 opens the LUKS partition <device> and sets up a mapping <name> after successful verification of the supplied key material (either via key file by \-\-key-file, or via prompting).
-<options> can be [\-\-key-file, \-\-readonly].
+<options> can be [\-\-key-file, \-\-readonly, \-\-network-password, \-\-password-port].
 .PP
 \fIluksClose\fR <name>
 .IP
@@ -200,11 +200,21 @@
 useful to align the filesystem at full stripe boundaries so it can take advantage of the RAID's geometry.  See for instance the sunit and swidth options
 in the mkfs.xfs manual page. By default, the payload is aligned at an 8 sector (4096 byte) boundary.
 .TP
+.B "\-\-network-password"
+Enable reading a passphrase from a TCP connection, as well as the current terminal.  This option allows remote booting a machine that would normally require a human to enter the passphrase at boot time.  Network passphrase support is disabled by default.  This option has no effect (network passphrase support remains disabled) if used in conjunction with \-\-key-file, or with any \fB<action>\fR other than \fIluksOpen\fR and \fIluksResume\fR.
+
+Note that the passphrase must currently be transmitted in cleartext, so this is \fInot\fR safe over any untrusted network link.  Note also that a newline (or, to support telnet as a client, a carriage return followed by a newline) is required just before the end of the TCP stream, and is discarded from the passphrase.  See \fBNOTES ON PASSWORD PROCESSING\fR below.
+
+A suitable invocation to send the passphrase to this program would be "echo 'passphrase' >/dev/tcp/\fIa.b.c.d\fR/\fIport\fR", where \fIa.b.c.d\fR is the IP address of this machine, and \fIport\fR is the TCP port this program listens on (4994 by default, but see \fB\-\-password-port\fR below).  The above invocation assumes your shell is bash, and it was configured with \-\-enable-net-redirections when it was built; if either of these assumptions is false, then you can also use tools like \fInc(1)\fR, or \fItelnet(1)\fR.  No data is returned over the connection.
+.TP
+.B "\-\-password-port=\fIport\fR"
+Listen on TCP port \fIport\fR for a passphrase, instead of the default 4994.
+.TP
 .B "\-\-version"
 Show the version.
 
 .SH NOTES ON PASSWORD PROCESSING
-\fIFrom a file descriptor or a terminal\fR: Password processing is new-line sensitive, meaning the reading will stop after encountering \\n. It will process the read material (without newline) with the default hash or the hash given by \-\-hash. After hashing, it will be cropped to the key size given by \-s.
+\fIFrom a file descriptor, network connection, or a terminal\fR: Password processing is new-line sensitive, meaning the reading will stop after encountering \\n. It will process the read material (without the trailing newline -- or carriage-return-newline sequence, if the data came from the network) with the default hash or the hash given by \-\-hash. After hashing, it will be cropped to the key size given by \-s.
 
 \fIFrom stdin\fR: Reading will continue until EOF (so using e.g. /dev/random as stdin will not work), with the trailing newline stripped. After that the read data will be hashed with the default hash or the hash given by \-\-hash and the result will be cropped to the keysize given by \-s. If "plain" is used as an argument to the hash option, the input data will not be hashed.
 Instead, it will be zero padded (if shorter than the keysize) or truncated (if longer than the keysize) and used directly as the key. No warning will be given if the amount of data read from stdin is less than the keysize.
_______________________________________________
dm-crypt mailing list
dm-crypt@xxxxxxxx
http://www.saout.de/mailman/listinfo/dm-crypt

[Index of Archives]     [Device Mapper Devel]     [Fedora Desktop]     [ATA RAID]     [Fedora Marketing]     [Fedora Packaging]     [Fedora SELinux]     [Yosemite News]     [KDE Users]     [Fedora Tools]     [Fedora Docs]

  Powered by Linux