diff options
author | Nick Mathewson <nickm@torproject.org> | 2003-09-04 16:05:08 +0000 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2003-09-04 16:05:08 +0000 |
commit | fd20011c263df7fa843d32a2323bff81357b5a20 (patch) | |
tree | 162072e508db0eea1be73e580752178449f66b94 /src/common/tortls.c | |
parent | 4fb92e5bf7144ec14f664913cfefb045fe813dfa (diff) | |
download | tor-fd20011c263df7fa843d32a2323bff81357b5a20.tar.gz tor-fd20011c263df7fa843d32a2323bff81357b5a20.zip |
Add initial interfaces and code for TLS support. Interfaces are right; code needs work and testing.
svn:r424
Diffstat (limited to 'src/common/tortls.c')
-rw-r--r-- | src/common/tortls.c | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/src/common/tortls.c b/src/common/tortls.c new file mode 100644 index 0000000000..9e964de6e6 --- /dev/null +++ b/src/common/tortls.c @@ -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; + } +} |