diff options
Diffstat (limited to 'source4/lib/tls/tls_tstream.c')
-rw-r--r-- | source4/lib/tls/tls_tstream.c | 1467 |
1 files changed, 1467 insertions, 0 deletions
diff --git a/source4/lib/tls/tls_tstream.c b/source4/lib/tls/tls_tstream.c new file mode 100644 index 0000000..d984add --- /dev/null +++ b/source4/lib/tls/tls_tstream.c @@ -0,0 +1,1467 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#include "system/filesys.h" +#include "system/time.h" +#include "../util/tevent_unix.h" +#include "../lib/tsocket/tsocket.h" +#include "../lib/tsocket/tsocket_internal.h" +#include "../lib/util/util_net.h" +#include "lib/tls/tls.h" + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#define DH_BITS 2048 + +const char *tls_verify_peer_string(enum tls_verify_peer_state verify_peer) +{ + switch (verify_peer) { + case TLS_VERIFY_PEER_NO_CHECK: + return TLS_VERIFY_PEER_NO_CHECK_STRING; + + case TLS_VERIFY_PEER_CA_ONLY: + return TLS_VERIFY_PEER_CA_ONLY_STRING; + + case TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE: + return TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE_STRING; + + case TLS_VERIFY_PEER_CA_AND_NAME: + return TLS_VERIFY_PEER_CA_AND_NAME_STRING; + + case TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE: + return TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE_STRING; + } + + return "unknown tls_verify_peer_state"; +} + +static const struct tstream_context_ops tstream_tls_ops; + +struct tstream_tls { + struct tstream_context *plain_stream; + int error; + + gnutls_session_t tls_session; + + enum tls_verify_peer_state verify_peer; + const char *peer_name; + + struct tevent_context *current_ev; + + struct tevent_immediate *retry_im; + + struct { + uint8_t *buf; + off_t ofs; + struct iovec iov; + struct tevent_req *subreq; + struct tevent_immediate *im; + } push; + + struct { + uint8_t *buf; + struct iovec iov; + struct tevent_req *subreq; + } pull; + + struct { + struct tevent_req *req; + } handshake; + + struct { + off_t ofs; + size_t left; + uint8_t buffer[1024]; + struct tevent_req *req; + } write; + + struct { + off_t ofs; + size_t left; + uint8_t buffer[1024]; + struct tevent_req *req; + } read; + + struct { + struct tevent_req *req; + } disconnect; +}; + +static void tstream_tls_retry_handshake(struct tstream_context *stream); +static void tstream_tls_retry_read(struct tstream_context *stream); +static void tstream_tls_retry_write(struct tstream_context *stream); +static void tstream_tls_retry_disconnect(struct tstream_context *stream); +static void tstream_tls_retry_trigger(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data); + +static void tstream_tls_retry(struct tstream_context *stream, bool deferred) +{ + + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + + if (tlss->disconnect.req) { + tstream_tls_retry_disconnect(stream); + return; + } + + if (tlss->handshake.req) { + tstream_tls_retry_handshake(stream); + return; + } + + if (tlss->write.req && tlss->read.req && !deferred) { + tevent_schedule_immediate(tlss->retry_im, tlss->current_ev, + tstream_tls_retry_trigger, + stream); + } + + if (tlss->write.req) { + tstream_tls_retry_write(stream); + return; + } + + if (tlss->read.req) { + tstream_tls_retry_read(stream); + return; + } +} + +static void tstream_tls_retry_trigger(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data) +{ + struct tstream_context *stream = + talloc_get_type_abort(private_data, + struct tstream_context); + + tstream_tls_retry(stream, true); +} + +static void tstream_tls_push_trigger_write(struct tevent_context *ev, + struct tevent_immediate *im, + void *private_data); + +static ssize_t tstream_tls_push_function(gnutls_transport_ptr_t ptr, + const void *buf, size_t size) +{ + struct tstream_context *stream = + talloc_get_type_abort(ptr, + struct tstream_context); + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + uint8_t *nbuf; + size_t len; + + if (tlss->error != 0) { + errno = tlss->error; + return -1; + } + + if (tlss->push.subreq) { + errno = EAGAIN; + return -1; + } + + len = MIN(size, UINT16_MAX - tlss->push.ofs); + + if (len == 0) { + errno = EAGAIN; + return -1; + } + + nbuf = talloc_realloc(tlss, tlss->push.buf, + uint8_t, tlss->push.ofs + len); + if (nbuf == NULL) { + if (tlss->push.buf) { + errno = EAGAIN; + return -1; + } + + return -1; + } + tlss->push.buf = nbuf; + + memcpy(tlss->push.buf + tlss->push.ofs, buf, len); + + if (tlss->push.im == NULL) { + tlss->push.im = tevent_create_immediate(tlss); + if (tlss->push.im == NULL) { + errno = ENOMEM; + return -1; + } + } + + if (tlss->push.ofs == 0) { + /* + * We'll do start the tstream_writev + * in the next event cycle. + * + * This way we can batch all push requests, + * if they fit into a UINT16_MAX buffer. + * + * This is important as gnutls_handshake() + * had a bug in some versions e.g. 2.4.1 + * and others (See bug #7218) and it doesn't + * handle EAGAIN. + */ + tevent_schedule_immediate(tlss->push.im, + tlss->current_ev, + tstream_tls_push_trigger_write, + stream); + } + + tlss->push.ofs += len; + return len; +} + +static void tstream_tls_push_done(struct tevent_req *subreq); + +static void tstream_tls_push_trigger_write(struct tevent_context *ev, + struct tevent_immediate *im, + void *private_data) +{ + struct tstream_context *stream = + talloc_get_type_abort(private_data, + struct tstream_context); + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *subreq; + + if (tlss->push.subreq) { + /* nothing todo */ + return; + } + + tlss->push.iov.iov_base = (char *)tlss->push.buf; + tlss->push.iov.iov_len = tlss->push.ofs; + + subreq = tstream_writev_send(tlss, + tlss->current_ev, + tlss->plain_stream, + &tlss->push.iov, 1); + if (subreq == NULL) { + tlss->error = ENOMEM; + tstream_tls_retry(stream, false); + return; + } + tevent_req_set_callback(subreq, tstream_tls_push_done, stream); + + tlss->push.subreq = subreq; +} + +static void tstream_tls_push_done(struct tevent_req *subreq) +{ + struct tstream_context *stream = + tevent_req_callback_data(subreq, + struct tstream_context); + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + int ret; + int sys_errno; + + tlss->push.subreq = NULL; + ZERO_STRUCT(tlss->push.iov); + TALLOC_FREE(tlss->push.buf); + tlss->push.ofs = 0; + + ret = tstream_writev_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tlss->error = sys_errno; + tstream_tls_retry(stream, false); + return; + } + + tstream_tls_retry(stream, false); +} + +static void tstream_tls_pull_done(struct tevent_req *subreq); + +static ssize_t tstream_tls_pull_function(gnutls_transport_ptr_t ptr, + void *buf, size_t size) +{ + struct tstream_context *stream = + talloc_get_type_abort(ptr, + struct tstream_context); + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *subreq; + size_t len; + + if (tlss->error != 0) { + errno = tlss->error; + return -1; + } + + if (tlss->pull.subreq) { + errno = EAGAIN; + return -1; + } + + if (tlss->pull.iov.iov_base) { + uint8_t *b; + size_t n; + + b = (uint8_t *)tlss->pull.iov.iov_base; + + n = MIN(tlss->pull.iov.iov_len, size); + memcpy(buf, b, n); + + tlss->pull.iov.iov_len -= n; + b += n; + tlss->pull.iov.iov_base = (char *)b; + if (tlss->pull.iov.iov_len == 0) { + tlss->pull.iov.iov_base = NULL; + TALLOC_FREE(tlss->pull.buf); + } + + return n; + } + + if (size == 0) { + return 0; + } + + len = MIN(size, UINT16_MAX); + + tlss->pull.buf = talloc_array(tlss, uint8_t, len); + if (tlss->pull.buf == NULL) { + return -1; + } + + tlss->pull.iov.iov_base = (char *)tlss->pull.buf; + tlss->pull.iov.iov_len = len; + + 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, stream); + + tlss->pull.subreq = subreq; + errno = EAGAIN; + return -1; +} + +static void tstream_tls_pull_done(struct tevent_req *subreq) +{ + struct tstream_context *stream = + tevent_req_callback_data(subreq, + struct tstream_context); + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + int ret; + int sys_errno; + + tlss->pull.subreq = NULL; + + ret = tstream_readv_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tlss->error = sys_errno; + tstream_tls_retry(stream, false); + return; + } + + tstream_tls_retry(stream, false); +} + +static int tstream_tls_destructor(struct tstream_tls *tlss) +{ + 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); + size_t ret; + + if (tlss->error != 0) { + errno = tlss->error; + return -1; + } + + ret = gnutls_record_check_pending(tlss->tls_session); + ret += tlss->read.left; + + return ret; +} + +struct tstream_tls_readv_state { + struct tstream_context *stream; + + struct iovec *vector; + int count; + + int ret; +}; + +static void tstream_tls_readv_crypt_next(struct tevent_req *req); + +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 tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req; + struct tstream_tls_readv_state *state; + + tlss->read.req = NULL; + tlss->current_ev = ev; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_readv_state); + if (req == NULL) { + return NULL; + } + + state->stream = stream; + state->ret = 0; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return tevent_req_post(req, ev); + } + + /* + * we make a copy of the vector so we can change the structure + */ + state->vector = talloc_array(state, struct iovec, count); + if (tevent_req_nomem(state->vector, req)) { + return tevent_req_post(req, ev); + } + memcpy(state->vector, vector, sizeof(struct iovec) * count); + state->count = count; + + tstream_tls_readv_crypt_next(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_tls_readv_crypt_next(struct tevent_req *req) +{ + struct tstream_tls_readv_state *state = + tevent_req_data(req, + struct tstream_tls_readv_state); + struct tstream_tls *tlss = + tstream_context_data(state->stream, + struct tstream_tls); + + /* + * copy the pending buffer first + */ + while (tlss->read.left > 0 && state->count > 0) { + uint8_t *base = (uint8_t *)state->vector[0].iov_base; + size_t len = MIN(tlss->read.left, state->vector[0].iov_len); + + memcpy(base, tlss->read.buffer + tlss->read.ofs, len); + + base += len; + state->vector[0].iov_base = (char *) base; + state->vector[0].iov_len -= len; + + tlss->read.ofs += len; + tlss->read.left -= len; + + if (state->vector[0].iov_len == 0) { + state->vector += 1; + state->count -= 1; + } + + state->ret += len; + } + + if (state->count == 0) { + tevent_req_done(req); + return; + } + + tlss->read.req = req; + tstream_tls_retry_read(state->stream); +} + +static void tstream_tls_retry_read(struct tstream_context *stream) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req = tlss->read.req; + int ret; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return; + } + + tlss->read.left = 0; + tlss->read.ofs = 0; + + ret = gnutls_record_recv(tlss->tls_session, + tlss->read.buffer, + sizeof(tlss->read.buffer)); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + return; + } + + tlss->read.req = NULL; + + if (gnutls_error_is_fatal(ret) != 0) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (ret == 0) { + tlss->error = EPIPE; + tevent_req_error(req, tlss->error); + return; + } + + tlss->read.left = ret; + tstream_tls_readv_crypt_next(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); + struct tstream_tls *tlss = + tstream_context_data(state->stream, + struct tstream_tls); + int ret; + + tlss->read.req = NULL; + + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->ret; + } + + tevent_req_received(req); + return ret; +} + +struct tstream_tls_writev_state { + struct tstream_context *stream; + + struct iovec *vector; + int count; + + int ret; +}; + +static void tstream_tls_writev_crypt_next(struct tevent_req *req); + +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 tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req; + struct tstream_tls_writev_state *state; + + tlss->write.req = NULL; + tlss->current_ev = ev; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_writev_state); + if (req == NULL) { + return NULL; + } + + state->stream = stream; + state->ret = 0; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return tevent_req_post(req, ev); + } + + /* + * we make a copy of the vector so we can change the structure + */ + state->vector = talloc_array(state, struct iovec, count); + if (tevent_req_nomem(state->vector, req)) { + return tevent_req_post(req, ev); + } + memcpy(state->vector, vector, sizeof(struct iovec) * count); + state->count = count; + + tstream_tls_writev_crypt_next(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_tls_writev_crypt_next(struct tevent_req *req) +{ + struct tstream_tls_writev_state *state = + tevent_req_data(req, + struct tstream_tls_writev_state); + struct tstream_tls *tlss = + tstream_context_data(state->stream, + struct tstream_tls); + + tlss->write.left = sizeof(tlss->write.buffer); + tlss->write.ofs = 0; + + /* + * first fill our buffer + */ + while (tlss->write.left > 0 && state->count > 0) { + uint8_t *base = (uint8_t *)state->vector[0].iov_base; + size_t len = MIN(tlss->write.left, state->vector[0].iov_len); + + memcpy(tlss->write.buffer + tlss->write.ofs, base, len); + + base += len; + state->vector[0].iov_base = (char *) base; + state->vector[0].iov_len -= len; + + tlss->write.ofs += len; + tlss->write.left -= len; + + if (state->vector[0].iov_len == 0) { + state->vector += 1; + state->count -= 1; + } + + state->ret += len; + } + + if (tlss->write.ofs == 0) { + tevent_req_done(req); + return; + } + + tlss->write.left = tlss->write.ofs; + tlss->write.ofs = 0; + + tlss->write.req = req; + tstream_tls_retry_write(state->stream); +} + +static void tstream_tls_retry_write(struct tstream_context *stream) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req = tlss->write.req; + int ret; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return; + } + + ret = gnutls_record_send(tlss->tls_session, + tlss->write.buffer + tlss->write.ofs, + tlss->write.left); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + return; + } + + tlss->write.req = NULL; + + if (gnutls_error_is_fatal(ret) != 0) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (ret == 0) { + tlss->error = EPIPE; + tevent_req_error(req, tlss->error); + return; + } + + tlss->write.ofs += ret; + tlss->write.left -= ret; + + if (tlss->write.left > 0) { + tlss->write.req = req; + tstream_tls_retry_write(stream); + return; + } + + tstream_tls_writev_crypt_next(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); + struct tstream_tls *tlss = + tstream_context_data(state->stream, + struct tstream_tls); + int ret; + + tlss->write.req = NULL; + + 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; + + tlss->disconnect.req = NULL; + tlss->current_ev = ev; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_disconnect_state); + if (req == NULL) { + return NULL; + } + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return tevent_req_post(req, ev); + } + + tlss->disconnect.req = req; + tstream_tls_retry_disconnect(stream); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_tls_retry_disconnect(struct tstream_context *stream) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req = tlss->disconnect.req; + int ret; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return; + } + + ret = gnutls_bye(tlss->tls_session, GNUTLS_SHUT_WR); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + return; + } + + tlss->disconnect.req = NULL; + + if (gnutls_error_is_fatal(ret) != 0) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + tevent_req_done(req); +} + +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 { + gnutls_certificate_credentials_t x509_cred; + gnutls_dh_params_t dh_params; + const char *tls_priority; + bool tls_enabled; + enum tls_verify_peer_state verify_peer; + const char *peer_name; +}; + +static int tstream_tls_params_destructor(struct tstream_tls_params *tlsp) +{ + if (tlsp->x509_cred) { + gnutls_certificate_free_credentials(tlsp->x509_cred); + tlsp->x509_cred = NULL; + } + if (tlsp->dh_params) { + gnutls_dh_params_deinit(tlsp->dh_params); + tlsp->dh_params = NULL; + } + + return 0; +} + +bool tstream_tls_params_enabled(struct tstream_tls_params *tlsp) +{ + return tlsp->tls_enabled; +} + +NTSTATUS tstream_tls_params_client(TALLOC_CTX *mem_ctx, + const char *ca_file, + const char *crl_file, + const char *tls_priority, + enum tls_verify_peer_state verify_peer, + const char *peer_name, + struct tstream_tls_params **_tlsp) +{ + struct tstream_tls_params *tlsp; + int ret; + + tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); + NT_STATUS_HAVE_NO_MEMORY(tlsp); + + talloc_set_destructor(tlsp, tstream_tls_params_destructor); + + tlsp->verify_peer = verify_peer; + if (peer_name != NULL) { + tlsp->peer_name = talloc_strdup(tlsp, peer_name); + if (tlsp->peer_name == NULL) { + talloc_free(tlsp); + return NT_STATUS_NO_MEMORY; + } + } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_CA_AND_NAME) { + DEBUG(0,("TLS failed to missing peer_name - " + "with 'tls verify peer = %s'\n", + tls_verify_peer_string(tlsp->verify_peer))); + talloc_free(tlsp); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + ret = gnutls_certificate_allocate_credentials(&tlsp->x509_cred); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + talloc_free(tlsp); + return NT_STATUS_NO_MEMORY; + } + + if (ca_file && *ca_file && file_exist(ca_file)) { + ret = gnutls_certificate_set_x509_trust_file(tlsp->x509_cred, + ca_file, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + DEBUG(0,("TLS failed to initialise cafile %s - %s\n", + ca_file, gnutls_strerror(ret))); + talloc_free(tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_CA_ONLY) { + DEBUG(0,("TLS failed to missing cafile %s - " + "with 'tls verify peer = %s'\n", + ca_file, + tls_verify_peer_string(tlsp->verify_peer))); + talloc_free(tlsp); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (crl_file && *crl_file && file_exist(crl_file)) { + ret = gnutls_certificate_set_x509_crl_file(tlsp->x509_cred, + crl_file, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + DEBUG(0,("TLS failed to initialise crlfile %s - %s\n", + crl_file, gnutls_strerror(ret))); + talloc_free(tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE) { + DEBUG(0,("TLS failed to missing crlfile %s - " + "with 'tls verify peer = %s'\n", + crl_file, + tls_verify_peer_string(tlsp->verify_peer))); + talloc_free(tlsp); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + tlsp->tls_priority = talloc_strdup(tlsp, tls_priority); + if (tlsp->tls_priority == NULL) { + talloc_free(tlsp); + return NT_STATUS_NO_MEMORY; + } + + tlsp->tls_enabled = true; + + *_tlsp = tlsp; + return NT_STATUS_OK; +} + +struct tstream_tls_connect_state { + 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; + const char *error_pos; + struct tstream_tls *tlss; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_connect_state); + if (req == NULL) { + return NULL; + } + + 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; + tlss->verify_peer = tls_params->verify_peer; + if (tls_params->peer_name != NULL) { + tlss->peer_name = talloc_strdup(tlss, tls_params->peer_name); + if (tevent_req_nomem(tlss->peer_name, req)) { + return tevent_req_post(req, ev); + } + } + + tlss->current_ev = ev; + tlss->retry_im = tevent_create_immediate(tlss); + if (tevent_req_nomem(tlss->retry_im, req)) { + return tevent_req_post(req, ev); + } + + ret = gnutls_init(&tlss->tls_session, GNUTLS_CLIENT); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + ret = gnutls_set_default_priority(tlss->tls_session); + if (ret != GNUTLS_E_SUCCESS) { + DBG_ERR("TLS %s - %s. Failed to set default priorities\n", + __location__, gnutls_strerror(ret)); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + if (strlen(tls_params->tls_priority) > 0) { + ret = gnutls_priority_set_direct(tlss->tls_session, + tls_params->tls_priority, + &error_pos); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s. Check 'tls priority' option at '%s'\n", + __location__, gnutls_strerror(ret), error_pos)); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + } + + ret = gnutls_credentials_set(tlss->tls_session, + GNUTLS_CRD_CERTIFICATE, + tls_params->x509_cred); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + gnutls_transport_set_ptr(tlss->tls_session, + (gnutls_transport_ptr_t)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); + + tlss->handshake.req = req; + tstream_tls_retry_handshake(state->tls_stream); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +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; +} + +/* + initialise global tls state +*/ +NTSTATUS tstream_tls_params_server(TALLOC_CTX *mem_ctx, + const char *dns_host_name, + bool enabled, + const char *key_file, + const char *cert_file, + const char *ca_file, + const char *crl_file, + const char *dhp_file, + const char *tls_priority, + struct tstream_tls_params **_tlsp) +{ + struct tstream_tls_params *tlsp; + int ret; + struct stat st; + + if (!enabled || key_file == NULL || *key_file == 0) { + tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); + NT_STATUS_HAVE_NO_MEMORY(tlsp); + talloc_set_destructor(tlsp, tstream_tls_params_destructor); + tlsp->tls_enabled = false; + + *_tlsp = tlsp; + return NT_STATUS_OK; + } + + tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); + NT_STATUS_HAVE_NO_MEMORY(tlsp); + + talloc_set_destructor(tlsp, tstream_tls_params_destructor); + + if (!file_exist(ca_file)) { + tls_cert_generate(tlsp, dns_host_name, + key_file, cert_file, ca_file); + } + + if (file_exist(key_file) && + !file_check_permissions(key_file, geteuid(), 0600, &st)) + { + DEBUG(0, ("Invalid permissions on TLS private key file '%s':\n" + "owner uid %u should be %u, mode 0%o should be 0%o\n" + "This is known as CVE-2013-4476.\n" + "Removing all tls .pem files will cause an " + "auto-regeneration with the correct permissions.\n", + key_file, + (unsigned int)st.st_uid, geteuid(), + (unsigned int)(st.st_mode & 0777), 0600)); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + ret = gnutls_certificate_allocate_credentials(&tlsp->x509_cred); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + talloc_free(tlsp); + return NT_STATUS_NO_MEMORY; + } + + if (ca_file && *ca_file) { + ret = gnutls_certificate_set_x509_trust_file(tlsp->x509_cred, + ca_file, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + DEBUG(0,("TLS failed to initialise cafile %s - %s\n", + ca_file, gnutls_strerror(ret))); + talloc_free(tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } + + if (crl_file && *crl_file) { + ret = gnutls_certificate_set_x509_crl_file(tlsp->x509_cred, + crl_file, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + DEBUG(0,("TLS failed to initialise crlfile %s - %s\n", + crl_file, gnutls_strerror(ret))); + talloc_free(tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } + + ret = gnutls_certificate_set_x509_key_file(tlsp->x509_cred, + cert_file, key_file, + GNUTLS_X509_FMT_PEM); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS failed to initialise certfile %s and keyfile %s - %s\n", + cert_file, key_file, gnutls_strerror(ret))); + talloc_free(tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + ret = gnutls_dh_params_init(&tlsp->dh_params); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + talloc_free(tlsp); + return NT_STATUS_NO_MEMORY; + } + + if (dhp_file && *dhp_file) { + gnutls_datum_t dhparms; + size_t size; + + dhparms.data = (uint8_t *)file_load(dhp_file, &size, 0, tlsp); + + if (!dhparms.data) { + DEBUG(0,("TLS failed to read DH Parms from %s - %d:%s\n", + dhp_file, errno, strerror(errno))); + talloc_free(tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + dhparms.size = size; + + ret = gnutls_dh_params_import_pkcs3(tlsp->dh_params, + &dhparms, + GNUTLS_X509_FMT_PEM); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS failed to import pkcs3 %s - %s\n", + dhp_file, gnutls_strerror(ret))); + talloc_free(tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } else { + ret = gnutls_dh_params_generate2(tlsp->dh_params, DH_BITS); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS failed to generate dh_params - %s\n", + gnutls_strerror(ret))); + talloc_free(tlsp); + return NT_STATUS_INTERNAL_ERROR; + } + } + + gnutls_certificate_set_dh_params(tlsp->x509_cred, tlsp->dh_params); + + tlsp->tls_priority = talloc_strdup(tlsp, tls_priority); + if (tlsp->tls_priority == NULL) { + talloc_free(tlsp); + return NT_STATUS_NO_MEMORY; + } + + tlsp->tls_enabled = true; + + *_tlsp = tlsp; + return NT_STATUS_OK; +} + +struct tstream_tls_accept_state { + 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 *tlsp, + const char *location) +{ + struct tevent_req *req; + struct tstream_tls_accept_state *state; + struct tstream_tls *tlss; + const char *error_pos; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_accept_state); + if (req == NULL) { + return NULL; + } + + 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; + + tlss->current_ev = ev; + tlss->retry_im = tevent_create_immediate(tlss); + if (tevent_req_nomem(tlss->retry_im, req)) { + return tevent_req_post(req, ev); + } + + ret = gnutls_init(&tlss->tls_session, GNUTLS_SERVER); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + ret = gnutls_set_default_priority(tlss->tls_session); + if (ret != GNUTLS_E_SUCCESS) { + DBG_ERR("TLS %s - %s. Failed to set default priorities\n", + __location__, gnutls_strerror(ret)); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + if (strlen(tlsp->tls_priority) > 0) { + ret = gnutls_priority_set_direct(tlss->tls_session, + tlsp->tls_priority, + &error_pos); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s. Check 'tls priority' option at '%s'\n", + __location__, gnutls_strerror(ret), error_pos)); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + } + + ret = gnutls_credentials_set(tlss->tls_session, GNUTLS_CRD_CERTIFICATE, + tlsp->x509_cred); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tevent_req_error(req, EINVAL); + 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_t)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); + + tlss->handshake.req = req; + tstream_tls_retry_handshake(state->tls_stream); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_tls_retry_handshake(struct tstream_context *stream) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req = tlss->handshake.req; + int ret; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return; + } + + ret = gnutls_handshake(tlss->tls_session); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + return; + } + + tlss->handshake.req = NULL; + + if (gnutls_error_is_fatal(ret) != 0) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (tlss->verify_peer >= TLS_VERIFY_PEER_CA_ONLY) { + unsigned int status = UINT32_MAX; + bool ip = true; + const char *hostname = NULL; + + if (tlss->peer_name != NULL) { + ip = is_ipaddress(tlss->peer_name); + } + + if (!ip) { + hostname = tlss->peer_name; + } + + if (tlss->verify_peer == TLS_VERIFY_PEER_CA_ONLY) { + hostname = NULL; + } + + if (tlss->verify_peer >= TLS_VERIFY_PEER_CA_AND_NAME) { + if (hostname == NULL) { + DEBUG(1,("TLS %s - no hostname available for " + "verify_peer[%s] and peer_name[%s]\n", + __location__, + tls_verify_peer_string(tlss->verify_peer), + tlss->peer_name)); + tlss->error = EINVAL; + tevent_req_error(req, tlss->error); + return; + } + } + + ret = gnutls_certificate_verify_peers3(tlss->tls_session, + hostname, + &status); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (status != 0) { + DEBUG(1,("TLS %s - check failed for " + "verify_peer[%s] and peer_name[%s] " + "status 0x%x (%s%s%s%s%s%s%s%s)\n", + __location__, + tls_verify_peer_string(tlss->verify_peer), + tlss->peer_name, + status, + status & GNUTLS_CERT_INVALID ? "invalid " : "", + status & GNUTLS_CERT_REVOKED ? "revoked " : "", + status & GNUTLS_CERT_SIGNER_NOT_FOUND ? + "signer_not_found " : "", + status & GNUTLS_CERT_SIGNER_NOT_CA ? + "signer_not_ca " : "", + status & GNUTLS_CERT_INSECURE_ALGORITHM ? + "insecure_algorithm " : "", + status & GNUTLS_CERT_NOT_ACTIVATED ? + "not_activated " : "", + status & GNUTLS_CERT_EXPIRED ? + "expired " : "", + status & GNUTLS_CERT_UNEXPECTED_OWNER ? + "unexptected_owner " : "")); + tlss->error = EINVAL; + tevent_req_error(req, tlss->error); + return; + } + } + + tevent_req_done(req); +} + +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; +} |