Add initial interfaces and code for TLS support. Interfaces are right; code needs work and testing.

svn:r424
This commit is contained in:
Nick Mathewson 2003-09-04 16:05:08 +00:00
parent 4fb92e5bf7
commit fd20011c26
9 changed files with 422 additions and 159 deletions

View File

@ -36,7 +36,7 @@ AC_CACHE_CHECK([for OpenSSL directory], ac_cv_openssldir, [
for ssldir in $tryssldir "" /usr/local/openssl /usr/lib/openssl /usr/local/ssl /usr/lib/ssl /usr/local /usr/pkg /opt /opt/openssl ; do
CPPFLAGS="$saved_CPPFLAGS"
LDFLAGS="$saved_LDFLAGS"
LIBS="$saved_LIBS -lcrypto"
LIBS="$saved_LIBS -lcrypto -lssl"
# Skip directories if they don't exist
if test ! -z "$ssldir" -a ! -d "$ssldir" ; then
@ -126,7 +126,7 @@ if (test ! -z "$ac_cv_openssldir" && test "x$ac_cv_openssldir" != "x(system)") ;
fi
fi
fi
LIBS="$saved_LIBS -lcrypto"
LIBS="$saved_LIBS -lcrypto -lssl"
dnl The warning message here is no longer strictly accurate.

View File

@ -42,160 +42,32 @@ each node knows its predecessor and successor, but no others. Traffic
flowing down the circuit is unwrapped by a symmetric key at each node,
which reveals the downstream node.
2. Connections
2.1. Establishing connections to onion routers (ORs)
There are two ways to connect to an OR. The first is as an onion
proxy (OP), which allows any node to connect without providing any
authentication or name. The second is as another OR, which allows
strong authentication. In both cases the initiating party (called
the 'client') sets up shared keys with the listening OR (called the
'server').
proxy (OP), which allows the OP to authenticate the OR without
authenticating itself. The second is as another OR, which allows
mutual authentication.
Before the handshake begins, assume all parties know the {(1024-bit)
public key, IPV4 address, and port} triplet of each OR.
Tor uses TLS for link encryption, using the cipher suite
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA". An OR always sends a
self-signed X.509 certificate whose commonName is the server's
nickname, and whose public key is in the server directory.
All parties receiving certificates must confirm that the public
key is as it appears in the server directory, and close the
connection if it does not.
1. Client connects to server:
The client generates a pair of 16-byte symmetric keys (one
[K_f] for the 'forward' stream from client to server, and one
[K_b] for the 'backward' stream from server to client) to be
used for link encryption.
The client then generates a 'Client authentication' message [M]
containing:
(If client is an OP)
The number 1 to signify OP handshake [2 bytes]
Forward link key [K_f] [16 bytes]
Backward link key [K_b] [16 bytes]
[Total: 34 bytes]
(If client is an OR)
The number 2 to signify OR handshake [2 bytes]
The client's published IPV4 address [4 bytes]
The client's published port [2 bytes]
The server's published IPV4 address [4 bytes]
The server's published port [2 bytes]
The forward key [K_f] [16 bytes]
The backward key [K_b] [16 bytes]
[Total: 46 bytes]
The client then RSA-encrypts [M] with the server's public key
and PKCS1 padding to give an encrypted message.
The client then opens a TCP connection to the server, sends
the 128-byte RSA-encrypted data to the server, and waits for a
reply.
2. The server receives the first handshake:
The OR waits for 128 bytes of data, and decrypts the resulting
data with its private key, checking the PKCS1 padding. If
the padding is invalid, it closes the connection. If the tag
indicates the client is an OP, and the message is 34 bytes long,
it performs step 2a. If the tag indicates the client is an OR,
and the message is 46 bytes long, it performs step 2b. Else,
it closes the connection.
2a. If client is an OP:
The connection is established, and the OR is ready to receive
cells. The server sets its keys for this connection, setting K_f
to the client's K_b, and K_b to the client's K_f. The handshake
is complete.
2b. If the client is an OR:
The server checks the list of known ORs for one with the address
and port given in the client's authentication. If no such OR
is known, or if the server is already connected to that OR, the
server closes the current TCP connection and stops handshaking.
The server sets its keys for this connection, setting K_f to
the client's K_b, and K_b to the client's K_f.
The server then creates a server authentication message [M2] as
follows:
Client's handshake [M] [44 bytes]
A random nonce [N] [8 bytes]
[Total: 52 bytes]
The server encrypts M2 with the client's public key (found
from the list of known routers), using PKCS1 padding.
The server sends the 128-byte encrypted message to the client,
and waits for a reply.
3. Client authenticates to server.
Once the client has received 128 bytes, it decrypts them with
its public key, and checks the PKCS1 padding. If the padding
is invalid, or the decrypted message's length is other than 52
bytes, the client closes the TCP connection.
The client checks that the addresses and keys in the reply
message are the same as the ones it originally sent. If not,
it closes the TCP connection.
The client generates the following authentication message [M3]:
The client's published IPV4 address [4 bytes]
The client's published port [2 bytes]
The server's published IPV4 address [4 bytes]
The server's published port [2 bytes]
The server-generated nonce [N] [8 bytes]
[Total: 20 bytes]
Once again, the client encrypts this message using the
server's public key and PKCS1 padding, and sends the resulting
128-byte message to the server.
4. Server checks client authentication
The server once again waits to receive 128 bytes from the
client, decrypts the message with its private key, and checks
the PKCS1 padding. If the padding is incorrect, or if the
message's length is other than 20 bytes, the server closes the
TCP connection and stops handshaking.
If the addresses in the decrypted message M3 match those in M
and M2, and if the nonce in M3 is the same as in M2, the
handshake is complete, and the client and server begin sending
cells to one another. Otherwise, the server closes the TCP
connection.
2.2. Sending cells and link encryption
Once the handshake is complete, the two sides send cells
(specified below) to one another. Cells are sent serially,
encrypted with the AES-CTR keystream specified by the handshake
protocol. Over a connection, communicants encrypt outgoing cells
with the connection's K_f, and decrypt incoming cells with the
connection's K_b.
[Commentary: This means that OR/OP->OR connections are malleable; I
can flip bits in cells as they go across the wire, and see flipped
bits coming out the cells as they are decrypted at the next
server. I need to look more at the data format to see whether
this is exploitable, but if there's no integrity checking there
either, I suspect we may have an attack here. -NM]
[Yes, this protocol is open to tagging attacks. The payloads are
encrypted inside the network, so it's only at the edge node and beyond
that it's a worry. But adversaries can already count packets and
observe/modify timing. It's not worth putting in hashes; indeed, it
would be quite hard, because one of the sides of the circuit doesn't
know the keys that are used for de/encrypting at each hop, so couldn't
craft hashes anyway. See the Bandwidth Throttling (threat model)
thread on http://archives.seul.org/or/dev/Jul-2002/threads.html. -RD]
[Even if I don't control both sides of the connection, I can still
do evil stuff. For instance, if I can guess that a cell is a
TOPIC_COMMAND_BEGIN cell to www.slashdot.org:80 , I can change the
address and port to point to a machine I control. -NM]
[We're going to address this tagging issue with e2e-only hashes.
See TODO file. -RD]
Once a TLS connection is established, the two sides send cells
(specified below) to one another. Cells are sent serially. All
cells are 256 bytes long. Cells may be sent embedded in TLS
records of any size or divided across TLS records, but the framing
of TLS records should not leak information about the type or
contents of the cells.
OR-to-OR connections are never deliberately closed. OP-to-OR
connections are closed when the OP has no more circuits running
over a connection, and an amount of time (????) has passed.
3. Cell Packet format

View File

@ -3,7 +3,7 @@ noinst_LIBRARIES = libor.a
#CFLAGS = -Wall -Wpointer-arith -O2
libor_a_SOURCES = log.c crypto.c fakepoll.c util.c aes.c
libor_a_SOURCES = log.c crypto.c fakepoll.c util.c aes.c tortls.c
noinst_HEADERS = log.h crypto.h fakepoll.h test.h util.h aes.h torint.h
noinst_HEADERS = log.h crypto.h fakepoll.h test.h util.h aes.h torint.h tortls.h

View File

@ -696,11 +696,6 @@ int crypto_SHA_digest(unsigned char *m, int len, unsigned char *digest)
return (SHA1(m,len,digest) == NULL);
}
struct crypto_dh_env_st {
DH *dh;
};
static BIGNUM *dh_param_p = NULL;
static BIGNUM *dh_param_g = NULL;
@ -735,6 +730,7 @@ static void init_dh_param() {
supposedly it equals:
2^1024 - 2^960 - 1 + 2^64 * { [2^894 pi] + 129093 }.
*/
/* See also rfc 3536 */
r = BN_hex2bn(&p,
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"

View File

@ -7,6 +7,7 @@
#include <stdio.h>
#include <openssl/rsa.h>
#include <openssl/dh.h>
/* available encryption primitives */
#define CRYPTO_CIPHER_IDENTITY 0
@ -72,7 +73,10 @@ int base64_encode(char *dest, int destlen, char *src, int srclen);
int base64_decode(char *dest, int destlen, char *src, int srclen);
/* Key negotiation */
typedef struct crypto_dh_env_st crypto_dh_env_t;
typedef struct crypto_dh_env_st {
DH *dh;
} crypto_dh_env_t;
/* #define CRYPTO_DH_SIZE (1536 / 8) */
#define CRYPTO_DH_SIZE (1024 / 8)
crypto_dh_env_t *crypto_dh_new();

323
src/common/tortls.c Normal file
View File

@ -0,0 +1,323 @@
/* Copyright 2003 Roger Dingledine. */
/* See LICENSE for licensing information */
/* $Id$ */
/* TLS wrappers for The Onion Router. (Unlike other tor functions, these
* are prefixed with tor_ in order to avoid conflicting with OpenSSL
* functions and variables.)
*/
#include "./crypto.h"
#include "./tortls.h"
#include "./util.h"
#include <assert.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/tls1.h>
#include <openssl/asn1.h>
#include <openssl/bio.h>
struct tor_tls_context_st {
SSL_CTX *ctx;
};
struct tor_tls_st {
SSL *ssl;
int socket;
enum {
TOR_TLS_ST_HANDSHAKE, TOR_TLS_ST_OPEN, TOR_TLS_ST_GOTCLOSE,
TOR_TLS_ST_SENTCLOSE, TOR_TLS_ST_CLOSED
} state;
int isServer;
};
#define _TOR_TLS_SYSCALL -6
#define _TOR_TLS_ZERORETURN -5
static int
tor_tls_get_error(tor_tls *tls, int r, int extra)
{
int err = SSL_get_error(tls->ssl, r);
switch (err) {
case SSL_ERROR_NONE:
return TOR_TLS_DONE;
case SSL_ERROR_WANT_READ:
return TOR_TLS_WANTREAD;
case SSL_ERROR_WANT_WRITE:
return TOR_TLS_WANTWRITE;
case SSL_ERROR_SYSCALL:
return extra ? _TOR_TLS_SYSCALL : TOR_TLS_ERROR;
case SSL_ERROR_ZERO_RETURN:
return extra ? _TOR_TLS_ZERORETURN : TOR_TLS_ERROR;
default:
return TOR_TLS_ERROR;
}
}
static int always_accept_verify_cb(int preverify_ok,
X509_STORE_CTX *x509_ctx)
{
/* XXXX Actually, this needs to get more complicated. But for now,
XXXX always accept peer certs. */
return 1;
}
/* Generate a self-signed certificate with the private key 'rsa' and
* commonName 'nickname', and write it, PEM-encoded, to the file named
* by 'certfile'. Return 0 on success, -1 for failure.
*/
int
tor_tls_write_certificate(char *certfile, crypto_pk_env_t *rsa, char *nickname)
{
RSA *_rsa = NULL;
time_t start_time, end_time;
EVP_PKEY *pkey = NULL;
X509 *x509 = NULL;
X509_NAME *name = NULL;
BIO *out = NULL;
int nid;
start_time = time(NULL);
assert(rsa && rsa->type == CRYPTO_PK_RSA);
if (!(_rsa = RSAPrivateKey_dup((RSA*)rsa->key)))
return -1;
if (!(pkey = EVP_PKEY_new()))
return -1;
if (!(EVP_PKEY_assign_RSA(pkey, _rsa)))
return -1;
if (!(x509 = X509_new()))
return -1;
if (!(X509_set_version(x509, 2)))
return -1;
if (!(ASN1_INTEGER_set(X509_get_serialNumber(x509), (long)start_time)))
return -1;
if (!(name = X509_NAME_new()))
return -1;
if ((nid = OBJ_txt2nid("organizationName")) != NID_undef) return -1;
if (!(X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC,
"TOR", -1, -1, 0))) return -1;
if ((nid = OBJ_txt2nid("commonName")) != NID_undef) return -1;
if (!(X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC,
nickname, -1, -1, 0))) return -1;
if (!(X509_set_issuer_name(x509, name)))
return -1;
if (!(X509_set_subject_name(x509, name)))
return -1;
if (!X509_time_adj(X509_get_notBefore(x509),0,&start_time))
return -1;
end_time = start_time + 24*60*60*365;
if (!X509_time_adj(X509_get_notAfter(x509),0,&end_time))
return -1;
if (!X509_set_pubkey(x509, pkey))
return -1;
if (!X509_sign(x509, pkey, EVP_sha1()))
return -1;
if (!(out = BIO_new_file(certfile, "w")))
return -1;
if (!(PEM_write_bio_X509(out, x509)))
return -1;
BIO_free(out);
X509_free(x509);
EVP_PKEY_free(pkey);
X509_NAME_free(name);
return 0;
}
/* Create a new TLS context. If we are going to be using it as a
* server, it must have isServer set to true, certfile set to a
* filename for a certificate file, and RSA set to the private key
* used for that certificate.
*/
tor_tls_context *
tor_tls_context_new(char *certfile, crypto_pk_env_t *rsa, int isServer)
{
assert(!rsa || rsa->type == CRYPTO_PK_RSA);
assert((certfile && rsa) || (!certfile && !rsa));
crypto_dh_env_t *dh = NULL;
RSA *_rsa = NULL;
EVP_PKEY *pkey = NULL;
tor_tls_context *result;
result = tor_malloc(sizeof(tor_tls_context));
if (!(result->ctx = SSL_CTX_new(TLSv1_method())))
return NULL;
/* XXXX This should use AES, but we'll need to require OpenSSL 0.9.7 first */
if (!SSL_CTX_set_cipher_list(result->ctx, TLS1_TXT_DHE_DSS_WITH_RC4_128_SHA))
/* TLS1_TXT_DHE_RSA_WITH_AES_128_SHA)) */
return NULL;
if (certfile && !SSL_CTX_use_certificate_file(result->ctx,certfile,
SSL_FILETYPE_PEM))
return NULL;
SSL_CTX_set_session_cache_mode(result->ctx, SSL_SESS_CACHE_OFF);
if (rsa) {
if (!(_rsa = RSAPrivateKey_dup((RSA*)rsa->key)))
return NULL;
if (!(pkey = EVP_PKEY_new()))
return NULL;
if (!EVP_PKEY_assign_RSA(pkey, _rsa))
return NULL;
if (!SSL_CTX_use_PrivateKey(result->ctx, pkey))
return NULL;
EVP_PKEY_free(pkey);
if (certfile) {
if (!SSL_CTX_check_private_key(result->ctx))
return NULL;
}
}
dh = crypto_dh_new();
SSL_CTX_set_tmp_dh(result->ctx, dh->dh);
crypto_dh_free(dh);
SSL_CTX_set_verify(result->ctx, SSL_VERIFY_PEER,
always_accept_verify_cb);
return result;
}
/* Create a new TLS object from a TLS context, a filedescriptor, and
* a flag to determine whether it is functioning as a server.
*/
tor_tls *
tor_tls_new(tor_tls_context *ctx, int sock, int isServer)
{
tor_tls *result = tor_malloc(sizeof(tor_tls));
if (!(result->ssl = SSL_new(ctx->ctx)))
return NULL;
result->socket = sock;
SSL_set_fd(result->ssl, sock);
result->state = TOR_TLS_ST_HANDSHAKE;
result->isServer = isServer;
return result;
}
/* Release resources associated with a TLS object. Does not close the
* underlying file descriptor.
*/
void
tor_tls_free(tor_tls *tls)
{
SSL_free(tls->ssl);
free(tls);
}
/* Underlying function for TLS reading. Reads up to 'len' characters
* from 'tls' into 'cp'. On success, returns the number of characters
* read. On failure, returns TOR_TLS_ERROR, TOR_TLS_CLOSE,
* TOR_TLS_WANTREAD, or TOR_TLS_WANTWRITE.
*/
int
tor_tls_read(tor_tls *tls, char *cp, int len)
{
int r, err;
assert(tls && tls->ssl);
assert(tls->state == TOR_TLS_ST_OPEN);
r = SSL_read(tls->ssl, cp, len);
if (r > 0)
return r;
err = tor_tls_get_error(tls, r, 1);
if (err == _TOR_TLS_SYSCALL)
return TOR_TLS_ERROR;
else if (err == _TOR_TLS_ZERORETURN) {
tls->state = TOR_TLS_ST_CLOSED;
return TOR_TLS_CLOSE;
} else {
/* XXXX Make sure it's not TOR_TLS_DONE. */
return err;
}
}
/* Underlying function for TLS writing. Write up to 'n' characters
* from 'cp' onto 'tls'. On success, returns the number of characters
* written. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD,
* or TOR_TLS_WANTWRITE.
*/
int
tor_tls_write(tor_tls *tls, char *cp, int n)
{
int r, err;
assert(tls && tls->ssl);
assert(tls->state == TOR_TLS_ST_OPEN);
r = SSL_write(tls->ssl, cp, n);
err = tor_tls_get_error(tls, r, 1);
if (err == _TOR_TLS_ZERORETURN) {
/* should never happen XXXX */
return 0;
} else if (err == TOR_TLS_DONE) {
return r;
} else {
return err;
}
}
/* Perform initial handshake on 'tls'. When finished, returns
* TOR_TLS_DONE. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD,
* or TOR_TLS_WANNTWRITE.
*/
int
tor_tls_handshake(tor_tls *tls)
{
int r;
assert(tls && tls->ssl);
assert(tls->state == TOR_TLS_ST_HANDSHAKE);
if (tls->isServer) {
r = SSL_accept(tls->ssl);
} else {
r = SSL_connect(tls->ssl);
}
r = tor_tls_get_error(tls,r,0);
if (r == TOR_TLS_DONE) {
tls->state = TOR_TLS_ST_OPEN;
}
return r;
}
/* Shut down an open tls connection 'tls'. When finished, returns
* TOR_TLS_DONE. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD,
* or TOR_TLS_WANTWRITE.
*/
int
tor_tls_shutdown(tor_tls *tls)
{
int r, err;
char buf[128];
assert(tls && tls->ssl);
if (tls->state == TOR_TLS_ST_SENTCLOSE) {
do {
r = SSL_read(tls->ssl, buf, 128);
} while (r>0);
err = tor_tls_get_error(tls, r, 1);
if (err == _TOR_TLS_ZERORETURN) {
tls->state = TOR_TLS_ST_GOTCLOSE;
/* fall through */
} else {
if (err == _TOR_TLS_SYSCALL)
err = TOR_TLS_ERROR;
return err;
}
}
r = SSL_shutdown(tls->ssl);
if (r == 1) {
tls->state = TOR_TLS_ST_CLOSED;
return TOR_TLS_DONE;
}
err = tor_tls_get_error(tls, r, 1);
if (err == _TOR_TLS_SYSCALL)
return TOR_TLS_ST_CLOSED; /* XXXX is this right? */
else if (err == _TOR_TLS_ZERORETURN) {
if (tls->state == TOR_TLS_ST_GOTCLOSE ||
tls->state == TOR_TLS_ST_SENTCLOSE) {
/* XXXX log; unexpected. */
return TOR_TLS_ERROR;
}
tls->state = TOR_TLS_ST_SENTCLOSE;
return tor_tls_shutdown(tls);
} else {
/* XXXX log if not error. */
return err;
}
}

29
src/common/tortls.h Normal file
View File

@ -0,0 +1,29 @@
/* Copyright 2003 Roger Dingledine */
/* See LICENSE for licensing information */
/* $Id$ */
#ifndef _TORTLS_H
#define _TORTLS_H
#include "../common/crypto.h"
typedef struct tor_tls_context_st tor_tls_context;
typedef struct tor_tls_st tor_tls;
#define TOR_TLS_ERROR -4
#define TOR_TLS_CLOSE -3
#define TOR_TLS_WANTREAD -2
#define TOR_TLS_WANTWRITE -1
#define TOR_TLS_DONE 0
int tor_tls_write_certificate(char *certfile, crypto_pk_env_t *rsa, char *nickname);
tor_tls_context *tor_tls_context_new(char *certfile, crypto_pk_env_t *rsa, int isServer);
tor_tls *tor_tls_new(tor_tls_context *ctx, int sock, int isServer);
void tor_tls_free(tor_tls *tls);
int tor_tls_read(tor_tls *tls, char *cp, int len);
int tor_tls_write(tor_tls *tls, char *cp, int n);
int tor_tls_handshake(tor_tls *tls);
/* XXXX we need a function to check for validated, verified peer certs. */
int tor_tls_shutdown(tor_tls *tls);
#endif

View File

@ -86,6 +86,23 @@ int read_to_buf(int s, int at_most, char **buf, int *buflen, int *buf_datalen, i
}
}
int read_to_buf_tls(tor_tls *tls, int at_most, char **buf, int *buflen, int *buf_datalen) {
int r;
assert(tls && *buf && buflen && buf_datalen);
if (at_most > *buflen - *buf_datalen)
at_most = *buflen - *buf_datalen;
if (at_most == 0)
return 0;
r = tor_tls_read(tls, *buf+*buf_datalen, at_most);
if (r<0)
return r;
*buf_datalen += r;
return r;
}
int flush_buf(int s, char **buf, int *buflen, int *buf_flushlen, int *buf_datalen) {
/* push from buf onto s
@ -127,6 +144,22 @@ int flush_buf(int s, char **buf, int *buflen, int *buf_flushlen, int *buf_datale
}
}
int flush_buf_tls(tor_tls *tls, char **buf, int *buflen, int *buf_flushlen, int *buf_datalen)
{
int r;
assert(tls && *buf && buflen && buf_datalen);
if (*buf_flushlen == 0)
return 0;
r = tor_tls_write(tls, *buf, *buf_flushlen);
if (r < 0) {
return r;
}
*buf_datalen -= r;
*buf_flushlen -= r;
memmove(*buf, *buf+r, *buf_datalen);
return r;
}
int write_to_buf(char *string, int string_len,
char **buf, int *buflen, int *buf_datalen) {

View File

@ -90,6 +90,7 @@
#endif
#include "../common/crypto.h"
#include "../common/tortls.h"
#include "../common/log.h"
#include "../common/util.h"
@ -482,11 +483,17 @@ void buf_free(char *buf);
int read_to_buf(int s, int at_most, char **buf, int *buflen, int *buf_datalen, int *reached_eof);
/* grab from s, put onto buf, return how many bytes read */
int read_to_buf_tls(tor_tls *tls, int at_most, char **buf, int *buflen, int *buf_datalen);
/* grab from s, put onto buf, return how many bytes read or a TLS
* status (same status codes as tor_tls_read) */
int flush_buf(int s, char **buf, int *buflen, int *buf_flushlen, int *buf_datalen);
/* push from buf onto s
* then memmove to front of buf
* return -1 or how many bytes remain on the buf */
int flush_buf_tls(tor_tls *tls, char **buf, int *buflen, int *buf_flushlen, int *buf_datalen);
/* As flush_buf, but returns number of bytes written or TLS status
* (same status codes as tor_tls_write) */
int write_to_buf(char *string, int string_len,
char **buf, int *buflen, int *buf_datalen);
@ -494,7 +501,6 @@ int write_to_buf(char *string, int string_len,
* return total number of bytes on the buf
*/
int fetch_from_buf(char *string, int string_len,
char **buf, int *buflen, int *buf_datalen);
/* if there is string_len bytes in buf, write them onto string,