Skip to content

kuyagic/spiped

 
 

Repository files navigation

spiped design
=============

Introduction
------------

spiped (pronounced "ess-pipe-dee") is a utility for creating symmetrically
encrypted and authenticated pipes between socket addresses, so that one may
connect to one address (e.g., a UNIX socket on localhost) and transparently
have a connection established to another address (e.g., a UNIX socket on a
different system).  This is similar to 'ssh -L' functionality, but does not
use SSH and requires a pre-shared symmetric key.

Note that spiped:
1. Requires a strong key file: The file specified via the -k option should
have at least 256 bits of entropy.  ('dd if=/dev/urandom bs=32 count=1' is
your friend.)
2. Requires strong entropy from /dev/urandom.  (Make sure your kernel's
random number generator is seeded at boot time!)
3. Does not provide any protection against information leakage via packet
timing: Running telnet over spiped will protect a password from being directly
read from the network, but will not obscure the typing rhythm.
4. Can significantly increase bandwidth usage for interactive sessions: It
sends data in packets of 1024 bytes, and pads smaller messages up to this
length, so a 1 byte write could be expanded to 1024 bytes if it cannot be
coalesced with adjacent bytes.
5. Uses a symmetric key -- so anyone who can connect to an spiped "server" is
also able to impersonate it.

Usage
-----

usage: spiped {-e | -d} -s <source socket> -t <target socket> -k <key file>
    [-f] [-n <max # connections>] [-o <connection timeout>] [[-p <pidfile>] [-g]]

Options:
    -e
	Take unencrypted connections from the source socket and send
	encrypted connections to the target socket.
    -d
	Take encrypted connections from the source socket and send
	unencrypted connections to the target socket.
    -s <source socket>
	Address on which spiped should listen for incoming connections.
	Must be in one of the following formats:
		/absolute/path/to/unix/socket
		host.name:port
		[ip.v4.ad.dr]:port
		[ipv6::addr]:port
	Note that hostnames are resolved when spiped is launched and are not
	re-resolved later; thus if DNS entries change spiped will continue to
	connect to the expired address.
    -t <target socket>
	Address to which spiped should connect.
    -k <key file>
	Use the provided key file to authenticate and encrypt.
    -f
	Use fast/weak handshaking: This reduces the CPU time spent in the
	initial connection setup, at the expense of losing perfect forward
	secrecy.
    -n <max # connections>
	Limit on the number of simultaneous connections allowed.  Defaults
	to 100 connections.
    -o <connection timeout>
	Timeout, in seconds, after which an attempt to connect to the target
	or a protocol handshake will be aborted (and the connection dropped)
	if not completed.  Defaults to 5s.
    -p <pidfile>
	File to which spiped's process ID should be written.  Defaults to
	<source socket>.pid (in the current directory if <source socket> is
	not an absolute path). This option conflicts with the -g option. 
    -g
  Do not daemonize. Process stays in foreground so it can be managed by
  a service controller. This option conflicts with the -p option.


For example, one might run

# dd if=/dev/urandom bs=32 count=1 of=keyfile
# spiped -d -s '[0.0.0.0]:8025' -t '[127.0.0.1]:25' -k keyfile

on a server and after copying keyfile to the local system, run

# spiped -e -s '[127.0.0.1]:25' -t $SERVERNAME:8025 -k keyfile

so that you can configure your local system to send email via localhost:25
but have it delivered via your server's SMTP server instead.

Security requirements
---------------------

The user is responsible for ensuring that:
1. The key file contains 256 or more bits of entropy.
2. The same key file is not used for more than 2^64 connections.
3. Any individual connection does not transmit more than 2^64 bytes.

Encrypted protocol
------------------

The client and server share a key file with 256 or more bits of entropy.  On
launch, they read the key file and compute
    K = SHA256(key file).

When a connection is established:
C1. The client generates a 256-bit random value nonce_C and sends it.
S1. The server generates a 256-bit random value nonce_S and sends it.

C2. The client receives a 256-bit value nonce_S.
S2. The server receives a 256-bit value nonce_C.

C3/S3. Both parties now compute the 512-bit value
    dk_1 = PBKDF2-SHA256(K, nonce_C || nonce_S, 1)
and parse it as a pair of 256-bit values
    dhmac_C || dhmac_S = dk_1.

C4. The client picks* a value x_C and computes** y_C = 2^x_C mod p, where p is
the Diffie-Hellman "group #14" modulus, and h_C = HMAC-SHA256(dhmac_C, y_C).
The client sends y_C || h_C to the server.
S4. The server receives a 2304-bit value which it parses as y_C || h_C, where
y_C is 2048 bits and h_C is 256 bits; and drops the connection if h_C is not
equal to HMAC-SHA256(dhmac_C, y_C) or y_C >= p.

S5. The server picks* a value x_S and computes** y_S = 2^x_S mod p and
h_S = HMAC-SHA256(dhmac_S, y_S).  The server sends y_S || h_S to the client.
C5. The client receives a 2304-bit value which it parses as y_S || h_S, where
y_S is 2048 bits and h_S is 256 bits; and drops the connection if h_S is not
equal to HMAC-SHA256(dhmac_S, y_S) or y_S >= p.

C6. The client computes** y_SC = y_S^x_C mod p.
S6. The server computes** y_SC = y_C^x_S mod p.
(Note that these two compute values are identical.)

C7/S7. Both parties now compute the 1024-bit value
    dk_2 = PBKDF2-SHA256(K, nonce_C || nonce_S || y_SC, 1)
and parse it as a 4-tuple of 256-bit values
    E_C || H_C || E_S || H_S.

Thereafter, the client and server exchange 1060-byte packets P generated from
plaintext messages M of 1--1024 bytes
    msg_padded = P || ( 0x00 x (1024 - length(P))) || bigendian32(length(P))
    msg_encrypted = AES256-CTR(E, msg_padded, packet#)
    P = msg_encrypted || HMAC-SHA256(H, msg_encrypted || bigendian64(packet#))
where E and H are E_C and H_C or E_S and H_S depending on whether the packet
is being sent by the client or the server, and AES256-CTR is computed with
nonce equal to the packet #, which starts at zero and increments for each
packet sent in the same direction.

* The values x_C, x_S picked must either be 0 (if forward perfect secrecy
is not desired) or have 256 bits of entropy (if forward perfect secrecy is
desired).

** The values y_C, y_S, and y_SC are 2048 bits and big-endian.

Security proof
--------------
1. Under the random oracle model, K has at least 255 bits of entropy (it's a
256-bit hash computed from a value with at least 256 bits of entropy).

2. Provided that at least one party is following the protocol and the key
file has been used for fewer than 2^64 connections, the probability of the
tuple (K, nonce_C, nonce_S) being non-unique is less than 2^(-192).

3. Under the random oracle model, the probability of an attacker without
access to K guessing either of dhmac_C and dhmac_S is less than
    P(attacker guesses K) +
    P(the tuple has been input to the oracle before) +
    P(the attacker directly guesses),
which is less than
    2^(-255) + 2^(-192) + 2^(-255) = 2^(-192) + 2^(-254).

4. Consequently, in order for an attacker to convince a protocol-obeying
party that a tuple (y, h) is legitimate, the attacker must do at least 2^190
expected work (which we consider to be computationally infeasible and do not
consider any further).

5. If one of the parties opts to not have perfect forward secrecy, then the
value y_SC will be equal to 1 and dk_2 will have the same security properties
as dk_1, i.e., it will be computationally infeasible for an attacker without
access to K to compute dk_2.

6. If both parties opt for perfect forward secrecy, an attacker who can
compute y_SC has solved a Diffie-Hellman problem over the 2048-bit group #14,
which is (under the CDH assumption) computationally infeasible.

7. Consequently, if both parties opt for perfect forward secrecy, an attacker
who obtains access to K after the handshake has completed will continue to be
unable to compute dk_2 from information exchanged during the handshake.

8. Under the random oracle model, the packets P are indistinguishable from
random 1060-byte packets; thus no information about the keys used or the
plaintext being transmitted is revealed by post-key-exchange communications.

9. Because the values (msg_encrypted || bigendian(packet#)) are distinct for
each packet, under the random oracle model it is infeasible for an attacker
without access to the value H to generate a packet which will be accepted as
valid.

Code layout
-----------

main.c		-- Command-line parsing, initialization, and event loop.
conn.c		-- Accepts and sets up connections.
proto_handshake.c
		-- Performs the handshaking portion of the spiped protocol.
proto_pipe.c	-- Performs the data-shuttling portion of the spiped protocol.
pcrypt.c	-- Does the cryptographic work for the spiped protocol.
lib/*		-- Library code (mostly originating from tarsnap and kivaloo).

Releases

No releases published

Packages

No packages published

Languages

  • C 98.5%
  • Makefile 1.2%
  • C++ 0.3%