diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 0000000..4d60e61 --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,42 @@ +To build and install spiped, run: + +```bash +make BINDIR=/path/to/target/directory install +``` + +To install man pages, add `MAN1DIR=/path/to/man.1/directory` to the command +line (e.g., `MAN1DIR=/usr/local/man/man1` on FreeBSD). + +Spiped should build and run on any IEEE Std 1003.1 (POSIX) compliant +system which +1. Includes the Software Development Utilities option, +2. Has OpenSSL available via `-lcrypto` and `#include `, and +3. Provides `/dev/urandom`. + +On some platforms (Solaris, maybe others), additional compiler and/or linker +options are required to find OpenSSL or system libraries; these can be +provided by adding e.g., `CFLAGS="-I/path/to/openssl/headers"` (compiler option) +or `LDADD_EXTRA="-L/usr/sfw/lib -lsocket -lnsl"` (linker option) to the make +command line. + +On some platforms (OpenBSD prior to 5.4, and possibly others) you will need to +add `#include ` at the start of + +``` +lib/dnsthread/dnsthread.c +lib/util/sock_util.c +proto/proto_conn.c +spipe/main.c +spipe/pushbits.c +``` +due to a POSIX-compliance bug on those platforms. + +On some platforms (mostly Linuxes) it is possible to install OpenSSL libaries +wihout the associated header files; the header files are usually in packages +named "openssl-devel", "libssl-dev", or similar. + +If your OS provides random bytes via some mechanism other than `/dev/urandom`, +please make local changes to `lib/util/entropy.c` and notify the author. + +If spiped fails to build or run for other reasons, please notify the +author. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9e3b680 --- /dev/null +++ b/README.md @@ -0,0 +1,200 @@ +# 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. + +spipe (pronounced "ess-pipe") is a utility which acts as an spiped protocol +client (i.e., connects to an spiped daemon), taking input from the standard +input and writing data read back to the standard output. + +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. + +## Example usage + +To set up an encrypted and authenticated pipe for sending email between two +systems (in the author's case, from many systems around the internet to his +central SMTP server, which then relays email to the rest of the world), one +might run + +```bash +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 + +```bash +spiped -e -s '[127.0.0.1]:25' -t $SERVERNAME:8025 -k keyfile +``` + +at which point mail delivered via `localhost:25` on the local system will be +securely transmitted to port 25 on the server. + +You can also use spiped to protect SSH servers from attackers: Since data is +authenticated before being forwarded to the target, this can allow you to SSH +to a host while protecting you in the event that someone finds an exploitable +bug in the SSH daemon -- this serves the same purpose as port knocking or a +firewall which restricts source IP addresses which can connect to SSH. On the +SSH server, run + +```bash +dd if=/dev/urandom bs=32 count=1 of=/etc/ssh/spiped.key +spiped -d -s '[0.0.0.0]:8022' -t '[127.0.0.1]:22' -k /etc/ssh/spiped.key +``` + +then copy the server's `/etc/ssh/spiped.key` to `~/.ssh/spiped_HOSTNAME_key` on +your local system and add the lines + +``` + Host HOSTNAME + ProxyCommand spipe -t %h:8022 -k ~/.ssh/spiped_%h_key +``` + +to the `~/.ssh/config` file. This will cause `ssh HOSTNAME` to automatically +connect using the spipe client via the spiped daemon; you can then firewall +off all incoming traffic on port tcp/22. + +For a detailed list of the command-line options to spiped and spipe, see the +README files in the respective subdirectories. + +## 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 = M || ( 0x00 x (1024 - length(M))) || bigendian32(length(M)) + 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 + +- spiped/* -- Code specific to the spiped utility. + - main.c -- Command-line parsing, initialization, and event loop. + - dispatch.c -- Accepts connections and hands them off to protocol code. +- spipe/* -- Code specific to the spipe utility. + - main.c -- Command-line parsing, initialization, and event loop. + - pushbits.c -- Copies data between standard input/output and a socket. +- proto/* -- Implements the spiped protocol. + - _conn.c -- Manages the lifecycle of a connection. + - _handshake.c -- Performs the handshaking portion of the protocol. + - _pipe.c -- Performs the data-shuttling portion of the protocol. + - _crypt.c -- Does the cryptographic bits needed by _handshake and _pipe. +- lib/* -- Library code (mostly originating from tarsnap and kivaloo). diff --git a/STYLE.md b/STYLE.md new file mode 100644 index 0000000..5524a08 --- /dev/null +++ b/STYLE.md @@ -0,0 +1,168 @@ +# Code style + +In general, FreeBSD style(9) should be followed unless it is irrelevant +(e.g., `$FreeBSD$` tags). + +Functions with external linkage are declared like this: + +```c + /** + * module_func(arg1, arg2): + * Description of what the function does, referring to arguments as + * ${arg1} or suchlike. + */ + int module_func(void *, int); +``` + +The identical comment appears in the C file where the function is defined. + +Static functions may have the above form of comment, or simply a +`/* Brief description of what the function does. */` +line before the function. + +In general, functions should return `(int)(-1)` or `NULL` to indicate error. + +Errors should be printed via `warnp` (if `errno` is relevant) or `warn0` (if `errno` +is not relevant) when they are first detected and also at higher levels where +useful. As an exception to this, malloc failures (i.e., `errno = ENOMEM`) can +result in failure being passed back up the call chain without being printed +immediately. (Naturally, other errors can be passed back where a function +definition so specifies; e.g., `ENOENT` in cases where a file not existing is +not erronous.) + +The first statement in `main()`, after variable declarations, should be +`WARNP_INIT;` in order to set the program name used for printing warnings. + +In general, functions should be structured with one return statement per +status, e.g., one `return()` for success and one `return()` for failure. Errors +should be handled by using goto to enter the error return path, e.g., + +```c + int + foo(int bar) + { + + if (something fails) + goto err0; + /* ... */ + if (something else fails) + goto err1; + /* ... */ + if (yet another operation fails) + goto err2; + + /* Success! */ + return (0); + + err2: + /* Clean up something. */ + err1: + /* Clean up something else. */ + err0: + /* Failure! */ + return (-1); + } +``` + +As an exception to the above, if there is only one way for the function to +fail, the idioms + +```c + return (baz(bar)); +``` + +and + +```c + int rc; + + rc = baz(bar); + /* ... cleanup code here ... */ + return (rc); +``` + +are allowed; furthermore, in cases such as `foo_free()`, the idiom + +```c + if (we shouldn't do anything) + return; +``` +is preferred over + +```c + if (we shouldn't do anything) + goto done; +``` +at the start of a function. + +Headers should be included in the following groups, with a blank line after +each (non-empty) group: +1. ``, with `` first followed by others alphabetically. +2. ``, in alphabetical order. +3. `<*.h>`, in alphabetical order. +4. header files from `/lib/`, in alphabetical order. +5. header files from the program being built, in alphabetical order. +6. header files (usually just one) defining the interface for this C file. + +If `ssize_t` is needed, `` should be included to provide it. + +If `size_t` is needed, `` should be included to provide it unless +``, ``, ``, or `` is already required. + +If the C99 integer types (uint8_t, int64_t, etc.) are required, `` +should be included to provide them unless `` is already required. + +The type 'char' should only be used to represent human-readable characters +(input from users, output to users, pathnames, et cetera). The type +`char *` should normally be a NUL-terminated string. The types `signed +char` and `unsigned char` should never be used; C99 integer types should +be used instead. + +When a struct is referenced, the idiom + +```c + /* Opaque types. */ + struct foo; + + struct bar * bar_from_foo(struct foo *); +``` + +is preferable to + +```c + #include "foo.h" /* needed for struct foo */ + + struct bar * bar_from_foo(struct foo *); +``` + +unless there is some reason why the internal layout of struct `foo` is needed +(e.g., if struct bar contains a struct foo rather than a struct `foo *`). Such +struct declarations should be sorted alphabetically. + +The file `foo.c` should only export symbols of the following forms: + +- foo_* + - most symbols should be of this form. +- FOO_* / BAR_FOO_* + - allowed in cases where FOO or BAR_FOO is idiomatic (e.g., + MD5, HMAC_SHA256). +- foo() / defoo() / unfoo() + - where "foo" is a verb and this improves code clarity. + +Functions named `foo_free` should return `void`, and `foo_free(NULL)` should have +no effect. + +If static variables need to be initialized to `0` (or `NULL`) then they should be +explicitly declared that way; implicit initialization should not be used. + +In non-trivial code, comments should be included which describe in English +what is being done by the surrounding code with sufficient detail that if the +code were removed, it could be replaced based on reading the comments without +requiring any significant creativity. + +Comments and documentation should be written in en-GB-oed; i.e., with +the 'u' included in words such as "honour", "colour", and "neighbour", +and the ending '-ize' in words such as "organize" and "realize". The +Oxford (aka. serial) comma should be used in lists. Quotation marks +should be placed logically, i.e., not including punctuation marks which +do not form a logical part of the quoted text.