From 9ea4da4bbbf90396b9b0694c0bf91712afce44f4 Mon Sep 17 00:00:00 2001 From: Holger Hans Peter Freyther Date: Tue, 6 Sep 2016 11:38:56 +0200 Subject: server: Introduce tls mode for the server Using tls priority of NORMAL:+ANON-ECDH:+ANON-DH already allows a client to connect to a server and protect the data using tls. Generate the dh params on load (and do that for the client right now as well) but that will go away soon. Change-Id: Ifa2ad24c0a631573c259a3bf94b91a946ad9ec9d --- include/osmo-pcap/osmo_pcap_server.h | 15 +++++ include/osmo-pcap/osmo_tls.h | 10 +++ src/Makefile.am | 6 +- src/osmo_server_main.c | 3 + src/osmo_server_network.c | 74 +++++++++++++++++++++-- src/osmo_tls.c | 114 ++++++++++++++++++++++++++++++++++- 6 files changed, 215 insertions(+), 7 deletions(-) diff --git a/include/osmo-pcap/osmo_pcap_server.h b/include/osmo-pcap/osmo_pcap_server.h index 6facbe3..89c3df2 100644 --- a/include/osmo-pcap/osmo_pcap_server.h +++ b/include/osmo-pcap/osmo_pcap_server.h @@ -24,6 +24,7 @@ #define OSMO_PCAP_SERVER_H #include "wireformat.h" +#include "osmo_tls.h" #include #include @@ -35,6 +36,7 @@ #include +#include #include struct rate_ctr_group; @@ -94,6 +96,12 @@ struct osmo_pcap_conn { /* statistics */ struct rate_ctr_group *ctrg; + + /* tls */ + bool tls_use; + bool direct_read; + size_t tls_limit_read; + struct osmo_tls_session tls_session; }; struct osmo_pcap_server { @@ -109,6 +117,13 @@ struct osmo_pcap_server { void *zmq_ctx; void *zmq_publ; + /* tls base */ + unsigned tls_log_level; + char *tls_priority; + char *tls_capath; + char *tls_server_cert; + char *tls_server_key; + char *base_path; off_t max_size; diff --git a/include/osmo-pcap/osmo_tls.h b/include/osmo-pcap/osmo_tls.h index bfc813e..54fea4d 100644 --- a/include/osmo-pcap/osmo_tls.h +++ b/include/osmo-pcap/osmo_tls.h @@ -24,10 +24,13 @@ #include #include +#include struct osmo_fd; struct osmo_wqueue; struct osmo_pcap_client; +struct osmo_pcap_conn; +struct osmo_pcap_server; struct osmo_tls_session { bool in_use; @@ -38,6 +41,8 @@ struct osmo_tls_session { /* any credentials */ bool anon_alloc; gnutls_anon_client_credentials_t anon_cred; + bool anon_serv_alloc; + gnutls_anon_server_credentials_t anon_serv_cred; /* a x509 cert credential */ bool cert_alloc; @@ -53,6 +58,7 @@ struct osmo_tls_session { struct osmo_wqueue *wqueue; + int (*read)(struct osmo_tls_session *session); void (*error)(struct osmo_tls_session *session); void (*handshake_done)(struct osmo_tls_session *session); }; @@ -60,6 +66,10 @@ struct osmo_tls_session { void osmo_tls_init(void); bool osmo_tls_init_client_session(struct osmo_pcap_client *client); + +bool osmo_tls_init_server_session(struct osmo_pcap_conn *conn, struct osmo_pcap_server *server); void osmo_tls_release(struct osmo_tls_session *); int osmo_tls_client_bfd_cb(struct osmo_fd *fd, unsigned int what); + +size_t osmo_tls_pending(struct osmo_tls_session *session); diff --git a/src/Makefile.am b/src/Makefile.am index 83409db..0532acf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -10,5 +10,7 @@ osmo_pcap_client_LDADD = $(PCAP_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) \ $(LIBOSMOGSM_LIBS) $(LIBGNUTLS_LIBS) osmo_pcap_server_SOURCES = osmo_server_main.c osmo_common.c \ - osmo_server_vty.c osmo_server_network.c -osmo_pcap_server_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBZMQ_LIBS) + osmo_server_vty.c osmo_server_network.c \ + osmo_tls.c +osmo_pcap_server_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBZMQ_LIBS) \ + $(LIBGNUTLS_LIBS) diff --git a/src/osmo_server_main.c b/src/osmo_server_main.c index bb94ec4..27fb519 100644 --- a/src/osmo_server_main.c +++ b/src/osmo_server_main.c @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -216,6 +217,8 @@ int main(int argc, char **argv) osmo_init_ignore_signals(); signal(SIGHUP, &signal_handler); + osmo_tls_init(); + rc = telnet_init(tall_bsc_ctx, NULL, 4241); if (rc < 0) { LOGP(DCLIENT, LOGL_ERROR, "Failed to bind telnet interface\n"); diff --git a/src/osmo_server_network.c b/src/osmo_server_network.c index 52abb2b..1b7addc 100644 --- a/src/osmo_server_network.c +++ b/src/osmo_server_network.c @@ -132,6 +132,7 @@ static void close_connection(struct osmo_pcap_conn *conn) if (conn->rem_wq.bfd.fd >= 0) { close(conn->rem_wq.bfd.fd); conn->rem_wq.bfd.fd = -1; + osmo_tls_release(&conn->tls_session); osmo_fd_unregister(&conn->rem_wq.bfd); } @@ -319,9 +320,19 @@ struct osmo_pcap_conn *osmo_pcap_server_find(struct osmo_pcap_server *server, return conn; } +static int do_read_tls(struct osmo_pcap_conn *conn, void *buf, size_t want_size) +{ + size_t size = want_size; + if (conn->tls_limit_read && size > conn->tls_limit_read) + size = conn->tls_limit_read; + return gnutls_record_recv(conn->tls_session.session, buf, size); +} + static int do_read(struct osmo_pcap_conn *conn, void *buf, size_t size) { - return read(conn->rem_wq.bfd.fd, buf, size); + if (conn->direct_read) + return read(conn->rem_wq.bfd.fd, buf, size); + return do_read_tls(conn, buf, size); } static int read_cb_initial(struct osmo_pcap_conn *conn) @@ -425,6 +436,42 @@ static int read_cb(struct osmo_fd *fd) return 0; } +static void tls_error_cb(struct osmo_tls_session *session) +{ + struct osmo_pcap_conn *conn; + conn = container_of(session, struct osmo_pcap_conn, tls_session); + close_connection(conn); +} + +static int tls_read_cb(struct osmo_tls_session *session) +{ + struct osmo_pcap_conn *conn; + size_t pend; + int rc; + + conn = container_of(session, struct osmo_pcap_conn, tls_session); + conn->tls_limit_read = 0; + rc = dispatch_read(conn); + if (rc <= 0) + return rc; + + /** + * This is a weakness of a single select approach and the + * buffered reading here. We need to read everything as + * otherwise we do not receive a ready-read. But at the + * same time don't read more than is buffered! So cap what + * can be read right now. + */ + while ((pend = osmo_tls_pending(session)) > 0) { + conn->tls_limit_read = pend; + rc = dispatch_read(conn); + if (rc <= 0) + return rc; + } + + return 1; +} + static void new_connection(struct osmo_pcap_server *server, struct osmo_pcap_conn *client, int new_fd) { @@ -441,11 +488,24 @@ static void new_connection(struct osmo_pcap_server *server, rate_ctr_inc(&client->ctrg->ctr[PEER_CTR_CONNECT]); - client->rem_wq.bfd.data = client; - client->rem_wq.bfd.when = BSC_FD_READ; - client->rem_wq.read_cb = read_cb; client->state = STATE_INITIAL; client->pend = sizeof(*client->data); + + if (client->tls_use) { + if (!osmo_tls_init_server_session(client, server)) { + close_connection(client); + return; + } + client->tls_session.error = tls_error_cb; + client->tls_session.read = tls_read_cb; + client->direct_read = false; + } else { + client->rem_wq.bfd.cb = osmo_wqueue_bfd_cb; + client->rem_wq.bfd.data = client; + client->rem_wq.bfd.when = BSC_FD_READ; + client->rem_wq.read_cb = read_cb; + client->direct_read = true; + } } static int accept_cb(struct osmo_fd *fd, unsigned int when) @@ -478,6 +538,12 @@ static int accept_cb(struct osmo_fd *fd, unsigned int when) } rate_ctr_inc(&server->ctrg->ctr[SERVER_CTR_NOCLIENT]); + + /* + * TODO: In the future start with a tls handshake and see if we know + * this client. + */ + LOGP(DSERVER, LOGL_ERROR, "Failed to find client for %s\n", inet_ntoa(addr.sin_addr)); close(new_fd); diff --git a/src/osmo_tls.c b/src/osmo_tls.c index ae957e6..c42d242 100644 --- a/src/osmo_tls.c +++ b/src/osmo_tls.c @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -34,6 +35,16 @@ exit(1); \ } +static gnutls_dh_params_t dh_params; +static int generate_dh_params (void) +{ + unsigned int bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, + GNUTLS_SEC_PARAM_HIGH); + + LOGP(DTLS, LOGL_NOTICE, "Going to create DH params for %d bits\n", bits); + gnutls_dh_params_init (&dh_params); + return gnutls_dh_params_generate2 (dh_params, bits); +} static int cert_callback(gnutls_session_t tls_session, const gnutls_datum_t * req_ca_rdn, int nreqs, @@ -94,6 +105,8 @@ void osmo_tls_init(void) rc = gnutls_global_init(); CHECK_RC(rc, "init failed"); gnutls_global_set_log_function(tls_log_func); + rc = generate_dh_params(); + CHECK_RC(rc, "dh params failed"); } static int need_handshake(struct osmo_tls_session *tls_session) @@ -110,7 +123,8 @@ static int need_handshake(struct osmo_tls_session *tls_session) tls_session->wqueue->bfd.when = BSC_FD_READ; tls_session->need_handshake = false; release_keys(tls_session); - tls_session->handshake_done(tls_session); + if (tls_session->handshake_done) + tls_session->handshake_done(tls_session); } else if (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED) { LOGP(DTLS, LOGL_DEBUG, "rc=%d will wait for writable again.\n", rc); } else if (gnutls_error_is_fatal(rc)) { @@ -128,6 +142,9 @@ static int tls_read(struct osmo_tls_session *sess) char buf[1024]; int rc; + if (sess->read) + return sess->read(sess); + memset(buf, 0, sizeof(buf)); rc = gnutls_record_recv(sess->session, buf, sizeof(buf) - 1); return rc; @@ -238,6 +255,99 @@ static int load_keys(struct osmo_pcap_client *client) return 0; } +size_t osmo_tls_pending(struct osmo_tls_session *sess) +{ + return gnutls_record_check_pending(sess->session); +} + +bool osmo_tls_init_server_session(struct osmo_pcap_conn *conn, + struct osmo_pcap_server *server) +{ + struct osmo_tls_session *sess = &conn->tls_session; + struct osmo_wqueue *wq = &conn->rem_wq; + int rc; + + gnutls_global_set_log_level(server->tls_log_level); + + memset(sess, 0, sizeof(*sess)); + sess->in_use = sess->anon_alloc = sess->cert_alloc = false; + rc = gnutls_init(&sess->session, GNUTLS_SERVER | GNUTLS_NONBLOCK); + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "gnutls_init failed with rc=%d\n", rc); + return false; + } + gnutls_session_set_ptr(sess->session, sess); + sess->in_use = true; + + /* use default or string */ + if (server->tls_priority) { + const char *err; + rc = gnutls_priority_set_direct(sess->session, server->tls_priority, &err); + } else { + rc = gnutls_set_default_priority(sess->session); + } + + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "def prio failed with rc=%d\n", rc); + osmo_tls_release(sess); + return false; + } + + /* allow username/password operation */ + rc = gnutls_anon_allocate_server_credentials(&sess->anon_serv_cred); + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "Failed to allocate anon cred rc=%d\n", rc); + osmo_tls_release(sess); + return false; + } + sess->anon_serv_alloc = true; + + /* x509 certificate handling */ + rc = gnutls_certificate_allocate_credentials(&sess->cert_cred); + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "Failed to allocate x509 cred rc=%d\n", rc); + osmo_tls_release(sess); + return false; + } + sess->cert_alloc = true; + + /* set the credentials now */ +#warning "Anon?" + gnutls_anon_set_server_dh_params (sess->anon_serv_cred, dh_params); + gnutls_credentials_set(sess->session, GNUTLS_CRD_ANON, sess->anon_serv_cred); + gnutls_credentials_set(sess->session, GNUTLS_CRD_CERTIFICATE, sess->cert_cred); + + if (server->tls_capath) { + rc = gnutls_certificate_set_x509_trust_file( + sess->cert_cred, server->tls_capath, GNUTLS_X509_FMT_PEM); + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "Failed to load capath from path=%s rc=%d\n", + server->tls_capath, rc); + osmo_tls_release(sess); + return false; + } + } + +#if 0 + if (load_keys(client) != 0) { + osmo_tls_release(sess); + return false; + } +#endif + + #warning "TODO client certificates" + + gnutls_transport_set_int(sess->session, wq->bfd.fd); + gnutls_handshake_set_timeout(sess->session, + GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + wq->bfd.cb = osmo_tls_client_bfd_cb; + wq->bfd.data = sess; + wq->bfd.when = BSC_FD_READ | BSC_FD_WRITE; + sess->need_handshake = true; + sess->wqueue = wq; + return true; +} + bool osmo_tls_init_client_session(struct osmo_pcap_client *client) { struct osmo_tls_session *sess = &client->tls_session; @@ -345,6 +455,8 @@ void osmo_tls_release(struct osmo_tls_session *session) if (session->anon_alloc) gnutls_anon_free_client_credentials(session->anon_cred); + if (session->anon_serv_alloc) + gnutls_anon_free_server_credentials(session->anon_serv_cred); if (session->cert_alloc) gnutls_certificate_free_credentials(session->cert_cred); session->in_use = false; -- cgit v1.2.3