From 046d35497b6614f42777f334c1eb108f16c5d1cd Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 3 Feb 2010 14:36:10 +0100 Subject: [PATCH] HACK-TODO: tls_tstream... --- source4/lib/tls/config.mk | 5 +- source4/lib/tls/tls.h | 111 ++++++ source4/lib/tls/tls_tstream.c | 678 ++++++++++++++++++++++++++++++++++ 3 files changed, 792 insertions(+), 2 deletions(-) create mode 100644 source4/lib/tls/tls_tstream.c diff --git a/source4/lib/tls/config.mk b/source4/lib/tls/config.mk index 0e1978cc1b0a..fd8be55f3b31 100644 --- a/source4/lib/tls/config.mk +++ b/source4/lib/tls/config.mk @@ -1,5 +1,6 @@ [SUBSYSTEM::LIBTLS] PUBLIC_DEPENDENCIES = \ - LIBTALLOC GNUTLS GCRYPT LIBSAMBA-HOSTCONFIG samba_socket + LIBTALLOC GNUTLS GCRYPT LIBSAMBA-HOSTCONFIG samba_socket \ + TEVENT UTIL_TEVENT TSOCKET -LIBTLS_OBJ_FILES = $(addprefix $(libtlssrcdir)/, tls.o tlscert.o) +LIBTLS_OBJ_FILES = $(addprefix $(libtlssrcdir)/, tls.o tlscert.o tls_tstream.o) diff --git a/source4/lib/tls/tls.h b/source4/lib/tls/tls.h index c5bd5c87cc13..f80282b9ccd5 100644 --- a/source4/lib/tls/tls.h +++ b/source4/lib/tls/tls.h @@ -65,4 +65,115 @@ bool tls_support(struct tls_params *parms); const struct socket_ops *socket_tls_ops(enum socket_type type); +struct tstream_context; +struct tstream_tls_params; + +/** + * @brief Initiate a TLS tunnel on top of a given tstream + * + * @param[in] mem_ctx + * @param[in] ev + * + * @param[in] plain_stream The plain tstream which is used as transport. + * It's important that the caller keeps the "plain" + * tstream_context arround during the whole life + * time of the "tls" tstream_context! + * Note: tstream_disconnect_send()/recv() doesn't + * disconnect the "plain" tstream_context. + * + * @param[in] tls_params ... + * + * @return + * + * @see tstream_tls_connect_recv + */ +#ifdef DOXYGEN +struct tevent_req *tstream_tls_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *plain_stream, + struct tstream_tls_params *tls_params); +#else +struct tevent_req *_tstream_tls_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *plain_stream, + struct tstream_tls_params *tls_params, + const char *location); +#define tstream_tls_connect_send(mem_ctx, ev, plain_stream, tls_params); \ + _tstream_tls_connect_send(mem_ctx, ev, plain_stream, tls_params, __location__) +#endif + +/** + * @brief Receives the async result of tevent_tls_connect_send + * + * @param[in] req + * + * @param[out] perrno + * + * @param[in] mem_ctx + * + * @param[out] tls_stream + * + * @return + * + * @see tstream_tls_connect_send + */ +int tstream_tls_connect_recv(struct tevent_req *req, + int *perrno, + TALLOC_CTX *mem_ctx, + struct tstream_context **tls_stream); + +/** + * @brief Accept a TLS tunnel on top of a given tstream + * + * @param[in] mem_ctx + * @param[in] ev + * + * @param[in] plain_stream The plain tstream which is used as transport. + * It's important that the caller keeps the "plain" + * tstream_context arround during the whole life + * time of the "tls" tstream_context! + * Note: tstream_disconnect_send()/recv() doesn't + * disconnect the "plain" tstream_context. + * + * @param[in] tls_params ... + * + * @return + * + * @see tstream_tls_accept_recv + */ +#ifdef DOXYGEN +struct tevent_req *tstream_tls_accept_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *plain_stream, + struct tstream_tls_params *tls_params); +#else +struct tevent_req *_tstream_tls_accept_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *plain_stream, + struct tstream_tls_params *tls_params, + const char *location); +#define tstream_tls_accept_send(mem_ctx, ev, plain_stream, tls_params); \ + _tstream_tls_accept_send(mem_ctx, ev, plain_stream, tls_params, __location__) #endif + +/** + * @brief Receives the async result of tevent_tls_accept_send + * + * @param[in] req + * + * @param[out] perrno + * + * @param[in] mem_ctx + * + * @param[out] tls_stream + * + * @return + * + * @see tstream_tls_accept_send + */ +int tstream_tls_accept_recv(struct tevent_req *req, + int *perrno, + TALLOC_CTX *mem_ctx, + struct tstream_context **tls_stream); + +#endif /* _TLS_H_ */ diff --git a/source4/lib/tls/tls_tstream.c b/source4/lib/tls/tls_tstream.c new file mode 100644 index 000000000000..2e9d6e91f868 --- /dev/null +++ b/source4/lib/tls/tls_tstream.c @@ -0,0 +1,678 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "includes.h" +#include "system/network.h" +#include "../util/tevent_unix.h" +#include "../lib/tsocket/tsocket.h" +#include "../lib/tsocket/tsocket_internal.h" +#include "lib/tls/tls.h" +#include "gnutls/gnutls.h" + +#define DH_BITS 1024 + +static const struct tstream_context_ops tstream_tls_ops; + +struct tstream_tls { + struct tstream_context *plain_stream; + int plain_errno; + + gnutls_session tls_session; + int tls_error; + + gnutls_certificate_credentials xcred; + + struct tevent_context *current_ev; + + struct { + uint8_t buffer[1024]; + struct iovec iov; + } push, pull; +}; + +static void tstream_tls_schedule_retry(struct tstream_context *tls, + struct tevent_context *ev) +{ + /* TODO */ +} +static void tstream_tls_retry(struct tstream_context *tls) +{ + /* TODO */ +} + +static void tstream_tls_push_done(struct tevent_req *subreq); + +static ssize_t tstream_tls_push_function(gnutls_transport_ptr ptr, + const void *buf, size_t size) +{ + struct tstream_context *tls = talloc_get_type_abort(ptr, + struct tstream_context); + struct tstream_tls *tlss = tstream_context_data(tls, struct tstream_tls); + struct tevent_req *subreq; + + if (tlss->push.iov.iov_base) { + errno = EAGAIN; + return -1; + } + + tlss->push.iov.iov_base = tlss->push.buffer; + tlss->push.iov.iov_len = MIN(size, sizeof(tlss->push.buffer)); + + memcpy(tlss->push.buffer, buf, tlss->push.iov.iov_len); + + subreq = tstream_writev_send(tlss, + tlss->current_ev, + tlss->plain_stream, + &tlss->push.iov, 1); + if (subreq == NULL) { + errno = ENOMEM; + return -1; + } + tevent_req_set_callback(subreq, tstream_tls_push_done, tls); + + return tlss->push.iov.iov_len; +} + +static void tstream_tls_push_done(struct tevent_req *subreq) +{ + struct tstream_context *tls = tevent_req_callback_data(subreq, + struct tstream_context); + struct tstream_tls *tlss = tstream_context_data(tls, struct tstream_tls); + int ret; + int perrno; + + ZERO_STRUCT(tlss->push.iov); + + ret = tstream_writev_recv(subreq, &perrno); + TALLOC_FREE(subreq); + if (ret == -1) { + tlss->plain_errno = perrno; + tstream_tls_retry(tls); + return; + } + + tstream_tls_retry(tls); +} + +static void tstream_tls_pull_done(struct tevent_req *subreq); + +static ssize_t tstream_tls_pull_function(gnutls_transport_ptr ptr, + void *buf, size_t size) +{ + struct tstream_context *tls = talloc_get_type_abort(ptr, + struct tstream_context); + struct tstream_tls *tlss = tstream_context_data(tls, struct tstream_tls); + struct tevent_req *subreq; + + if (tlss->pull.iov.iov_base) { + size_t n; + + n = MIN(tlss->pull.iov.iov_len, size); + memcpy(buf, tlss->pull.iov.iov_base, n); + + tlss->pull.iov.iov_len -= n; + if (tlss->pull.iov.iov_len == 0) { + tlss->pull.iov.iov_base = NULL; + } + + return n; + } + + if (size == 0) { + return 0; + } + + tlss->pull.iov.iov_base = tlss->pull.buffer; + tlss->pull.iov.iov_len = MIN(size, sizeof(tlss->pull.buffer)); + + subreq = tstream_readv_send(tlss, + tlss->current_ev, + tlss->plain_stream, + &tlss->pull.iov, 1); + if (subreq == NULL) { + errno = ENOMEM; + return -1; + } + tevent_req_set_callback(subreq, tstream_tls_pull_done, tls); + + errno = EAGAIN; + return -1; +} + +static void tstream_tls_pull_done(struct tevent_req *subreq) +{ + struct tstream_context *tls = tevent_req_callback_data(subreq, + struct tstream_context); + struct tstream_tls *tlss = tstream_context_data(tls, struct tstream_tls); + int ret; + int perrno; + + ret = tstream_readv_recv(subreq, &perrno); + TALLOC_FREE(subreq); + if (ret == -1) { + tlss->plain_errno = perrno; + tstream_tls_retry(tls); + return; + } + + tstream_tls_retry(tls); +} + +static int tstream_tls_destructor(struct tstream_tls *tlss) +{ + if (tlss->xcred) { + gnutls_certificate_free_credentials(tlss->xcred); + tlss->xcred = NULL; + } + if (tlss->tls_session) { + gnutls_deinit(tlss->tls_session); + tlss->tls_session = NULL; + } + return 0; +} + +static ssize_t tstream_tls_pending_bytes(struct tstream_context *stream) +{ + struct tstream_tls *tlss = tstream_context_data(stream, + struct tstream_tls); + ssize_t ret; + + if (!tlss->plain_stream) { + errno = ENOTCONN; + return -1; + } + + if (!tlss->tls_session) { + ret = tstream_pending_bytes(tlss->plain_stream); + return ret; + } + + ret = gnutls_record_check_pending(tlss->tls_session); + if (ret < 0) { + /* TODO: better mapping */ + errno = EIO; + return -1; + } + + return ret; +} + +struct tstream_tls_readv_state { + int ret; +}; + +static void tstream_tls_readv_plain_handler(struct tevent_req *subreq); + +static struct tevent_req *tstream_tls_readv_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + struct iovec *vector, + size_t count) +{ + struct tevent_req *req; + struct tstream_tls_readv_state *state; + struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_readv_state); + if (!req) { + return NULL; + } + + state->ret = 0; + + if (!tlss->plain_stream) { + tevent_req_error(req, ENOTCONN); + return tevent_req_post(req, ev); + } + + if (!tlss->tls_session) { + subreq = tstream_readv_send(state, + ev, + tlss->plain_stream, + vector, + count); + if (tevent_req_nomem(subreq,req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + tstream_tls_readv_plain_handler, + req); + + return req; + } + + /* TODO */ + tevent_req_error(req, ENOSYS); + return tevent_req_post(req, ev); +} + +static void tstream_tls_readv_plain_handler(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct tstream_tls_readv_state *state = tevent_req_data(req, + struct tstream_tls_readv_state); + int ret; + int sys_errno; + + ret = tstream_readv_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tevent_req_error(req, sys_errno); + return; + } + + state->ret = ret; + + tevent_req_done(req); +} + +static int tstream_tls_readv_recv(struct tevent_req *req, + int *perrno) +{ + struct tstream_tls_readv_state *state = tevent_req_data(req, + struct tstream_tls_readv_state); + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->ret; + } + + tevent_req_received(req); + return ret; +} + +struct tstream_tls_writev_state { + int ret; +}; + +static void tstream_tls_writev_plain_handler(struct tevent_req *subreq); + +static struct tevent_req *tstream_tls_writev_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + const struct iovec *vector, + size_t count) +{ + struct tevent_req *req; + struct tstream_tls_writev_state *state; + struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_writev_state); + if (!req) { + return NULL; + } + + state->ret = 0; + + if (!tlss->plain_stream) { + tevent_req_error(req, ENOTCONN); + return tevent_req_post(req, ev); + } + + if (!tlss->tls_session) { + subreq = tstream_writev_send(state, + ev, + tlss->plain_stream, + vector, + count); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, tstream_tls_writev_plain_handler, req); + + return req; + } + + /* TODO */ + tevent_req_error(req, ENOSYS); + return tevent_req_post(req, ev); +} + +static void tstream_tls_writev_plain_handler(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct tstream_tls_writev_state *state = tevent_req_data(req, + struct tstream_tls_writev_state); + int ret; + int sys_errno; + + ret = tstream_writev_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tevent_req_error(req, sys_errno); + return; + } + + state->ret = ret; + + tevent_req_done(req); +} + +static int tstream_tls_writev_recv(struct tevent_req *req, + int *perrno) +{ + struct tstream_tls_writev_state *state = tevent_req_data(req, + struct tstream_tls_writev_state); + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->ret; + } + + tevent_req_received(req); + return ret; +} + +struct tstream_tls_disconnect_state { + uint8_t _dummy; +}; + +static struct tevent_req *tstream_tls_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream) +{ + struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); + struct tevent_req *req; + struct tstream_tls_disconnect_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_disconnect_state); + if (req == NULL) { + return NULL; + } + + if (!tlss->plain_stream) { + tevent_req_error(req, ENOTCONN); + return tevent_req_post(req, ev); + } + + if (!tlss->tls_session) { + /* + * The caller is responsible to do the real disconnect + * on the plain stream! + */ + tlss->plain_stream = NULL; + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + /* TODO */ + tevent_req_error(req, ENOSYS); + return tevent_req_post(req, ev); +} + +static int tstream_tls_disconnect_recv(struct tevent_req *req, + int *perrno) +{ + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + + tevent_req_received(req); + return ret; +} + +static const struct tstream_context_ops tstream_tls_ops = { + .name = "tls", + + .pending_bytes = tstream_tls_pending_bytes, + + .readv_send = tstream_tls_readv_send, + .readv_recv = tstream_tls_readv_recv, + + .writev_send = tstream_tls_writev_send, + .writev_recv = tstream_tls_writev_recv, + + .disconnect_send = tstream_tls_disconnect_send, + .disconnect_recv = tstream_tls_disconnect_recv, +}; + +struct tstream_tls_params { + const char *ca_path; + gnutls_certificate_credentials xcred; +}; + +struct tstream_tls_connect_state { + struct { + struct tevent_context *ev; + } caller; + struct tstream_context *tls_stream; +}; + +struct tevent_req *_tstream_tls_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *plain_stream, + struct tstream_tls_params *tls_params, + const char *location) +{ + struct tevent_req *req; + struct tstream_tls_connect_state *state; + struct tstream_tls *tlss; + int ret; + static const int cert_type_priority[] = { + GNUTLS_CRT_X509, + GNUTLS_CRT_OPENPGP, + 0 + }; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_connect_state); + if (!req) { + return NULL; + } + + state->caller.ev = ev; + + state->tls_stream = tstream_context_create(state, + &tstream_tls_ops, + &tlss, + struct tstream_tls, + location); + if (tevent_req_nomem(state->tls_stream, req)) { + return tevent_req_post(req, ev); + } + ZERO_STRUCTP(tlss); + talloc_set_destructor(tlss, tstream_tls_destructor); + + tlss->plain_stream = plain_stream; + + gnutls_global_init(); + + ret = gnutls_certificate_allocate_credentials(&tlss->xcred); + if (tevent_req_error(req, ret)) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + return tevent_req_post(req, ev); + } + + gnutls_certificate_set_x509_trust_file(tlss->xcred, + tls_params->ca_path, + GNUTLS_X509_FMT_PEM); + + ret = gnutls_init(&tlss->tls_session, GNUTLS_CLIENT); + if (tevent_req_error(req, ret)) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + return tevent_req_post(req, ev); + } + + ret = gnutls_set_default_priority(tlss->tls_session); + if (tevent_req_error(req, ret)) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + return tevent_req_post(req, ev); + } + + gnutls_certificate_type_set_priority(tlss->tls_session, cert_type_priority); + + ret = gnutls_credentials_set(tlss->tls_session, GNUTLS_CRD_CERTIFICATE, tlss->xcred); + if (tevent_req_error(req, ret)) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + return tevent_req_post(req, ev); + } + + gnutls_transport_set_ptr(tlss->tls_session, (gnutls_transport_ptr)state->tls_stream); + gnutls_transport_set_pull_function(tlss->tls_session, + (gnutls_pull_func)tstream_tls_pull_function); + gnutls_transport_set_push_function(tlss->tls_session, + (gnutls_push_func)tstream_tls_push_function); + gnutls_transport_set_lowat(tlss->tls_session, 0); + + //tstream_tls_prepare_operation(tls, state->caller.ev); + ret = gnutls_handshake(tlss->tls_session); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + tstream_tls_schedule_retry(state->tls_stream, state->caller.ev); + return req; + } + if (tevent_req_error(req, ret)) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + return tevent_req_post(req, ev); + } + + return tevent_req_post(req, ev); +} + +int tstream_tls_connect_recv(struct tevent_req *req, + int *perrno, + TALLOC_CTX *mem_ctx, + struct tstream_context **tls_stream) +{ + struct tstream_tls_connect_state *state = + tevent_req_data(req, + struct tstream_tls_connect_state); + + if (tevent_req_is_unix_error(req, perrno)) { + tevent_req_received(req); + return -1; + } + + *tls_stream = talloc_move(mem_ctx, &state->tls_stream); + tevent_req_received(req); + return 0; +} + +struct tstream_tls_accept_state { + struct { + struct tevent_context *ev; + } caller; + struct tstream_context *tls_stream; +}; + +struct tevent_req *_tstream_tls_accept_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *plain_stream, + struct tstream_tls_params *tls_params, + const char *location) +{ + struct tevent_req *req; + struct tstream_tls_accept_state *state; + struct tstream_tls *tlss; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_accept_state); + if (!req) { + return NULL; + } + + state->caller.ev = ev; + + state->tls_stream = tstream_context_create(state, + &tstream_tls_ops, + &tlss, + struct tstream_tls, + location); + if (tevent_req_nomem(state->tls_stream, req)) { + return tevent_req_post(req, ev); + } + ZERO_STRUCTP(tlss); + talloc_set_destructor(tlss, tstream_tls_destructor); + + tlss->plain_stream = plain_stream; + + gnutls_global_init(); + + ret = gnutls_init(&tlss->tls_session, GNUTLS_SERVER); + if (tevent_req_error(req, ret)) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + return tevent_req_post(req, ev); + } + + ret = gnutls_set_default_priority(tlss->tls_session); + if (tevent_req_error(req, ret)) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + return tevent_req_post(req, ev); + } + + ret = gnutls_credentials_set(tlss->tls_session, GNUTLS_CRD_CERTIFICATE, + tls_params->xcred); + if (tevent_req_error(req, ret)) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + return tevent_req_post(req, ev); + } + + gnutls_certificate_server_set_request(tlss->tls_session, + GNUTLS_CERT_REQUEST); + gnutls_dh_set_prime_bits(tlss->tls_session, DH_BITS); + + gnutls_transport_set_ptr(tlss->tls_session, (gnutls_transport_ptr)state->tls_stream); + gnutls_transport_set_pull_function(tlss->tls_session, + (gnutls_pull_func)tstream_tls_pull_function); + gnutls_transport_set_push_function(tlss->tls_session, + (gnutls_push_func)tstream_tls_push_function); + gnutls_transport_set_lowat(tlss->tls_session, 0); + + //tstream_tls_prepare_operation(tls, state->caller.ev); + ret = gnutls_handshake(tlss->tls_session); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + tstream_tls_schedule_retry(state->tls_stream, state->caller.ev); + return req; + } + if (tevent_req_error(req, ret)) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + return tevent_req_post(req, ev); + } + + return tevent_req_post(req, ev); +} + +int tstream_tls_accept_recv(struct tevent_req *req, + int *perrno, + TALLOC_CTX *mem_ctx, + struct tstream_context **tls_stream) +{ + struct tstream_tls_accept_state *state = + tevent_req_data(req, + struct tstream_tls_accept_state); + + if (tevent_req_is_unix_error(req, perrno)) { + tevent_req_received(req); + return -1; + } + + *tls_stream = talloc_move(mem_ctx, &state->tls_stream); + tevent_req_received(req); + return 0; +} + -- 2.34.1