diff options
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | TLS_TODO | 16 | ||||
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | contrib/osmo-pcap-client-tls.cfg | 16 | ||||
-rw-r--r-- | contrib/osmo-pcap-server-tls.cfg | 27 | ||||
-rw-r--r-- | doc/tls.txt | 76 | ||||
-rw-r--r-- | include/osmo-pcap/Makefile.am | 2 | ||||
-rw-r--r-- | include/osmo-pcap/common.h | 1 | ||||
-rw-r--r-- | include/osmo-pcap/osmo_pcap_client.h | 18 | ||||
-rw-r--r-- | include/osmo-pcap/osmo_pcap_server.h | 26 | ||||
-rw-r--r-- | include/osmo-pcap/osmo_tls.h | 80 | ||||
-rw-r--r-- | osmoappdesc.py | 4 | ||||
-rw-r--r-- | src/Makefile.am | 13 | ||||
-rw-r--r-- | src/osmo_client_main.c | 4 | ||||
-rw-r--r-- | src/osmo_client_network.c | 41 | ||||
-rw-r--r-- | src/osmo_client_vty.c | 194 | ||||
-rw-r--r-- | src/osmo_common.c | 6 | ||||
-rw-r--r-- | src/osmo_server_main.c | 5 | ||||
-rw-r--r-- | src/osmo_server_network.c | 170 | ||||
-rw-r--r-- | src/osmo_server_vty.c | 303 | ||||
-rw-r--r-- | src/osmo_tls.c | 558 |
21 files changed, 1501 insertions, 62 deletions
diff --git a/.travis.yml b/.travis.yml index f602bfd..ebec2d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: c os: - linux sudo: required +dist: trusty addons: apt: packages: @@ -18,6 +19,7 @@ addons: - libpcsclite-dev - libpcap-dev - libzmq3-dev + - libgnutls28-dev script: - contrib/travis.sh diff --git a/TLS_TODO b/TLS_TODO new file mode 100644 index 0000000..501f2a1 --- /dev/null +++ b/TLS_TODO @@ -0,0 +1,16 @@ += Goals + +Secure communication between client and server. The captured +data might go through different interfaces than the one used +for capturing. + +Instead of rolling a custom protocol the idea is to adopt TLS +1.2 to achieve client authentication and ciphering. + +Neither the client nor the server should block during the key +exchange. Most TLS implementations do block and this is a problem +for a single threaded server. Ideally the same library is used +in the client and the server. + +In practice libraries might block during the handshake and this +is a big deal for the server (other clients block). diff --git a/configure.ac b/configure.ac index fbd1331..4c0a12f 100644 --- a/configure.ac +++ b/configure.ac @@ -63,6 +63,7 @@ PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.3.2) PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.3.2) PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.3.0) PKG_CHECK_MODULES(LIBZMQ, libzmq >= 3.2.2) +PKG_CHECK_MODULES(LIBGNUTLS, gnutls) # Coverage build taken from WebKit's configure.in diff --git a/contrib/osmo-pcap-client-tls.cfg b/contrib/osmo-pcap-client-tls.cfg new file mode 100644 index 0000000..73d1535 --- /dev/null +++ b/contrib/osmo-pcap-client-tls.cfg @@ -0,0 +1,16 @@ +! +! OsmoPCAPClient (UNKNOWN-dirty) configuration saved from vty +!! +! +! +line vty + no login +! +client + pcap device any + pcap filter host www.google.com + pcap detect-loop 0 + server ip 127.0.0.1 + server port 6001 + enable tls + tls priority NORMAL diff --git a/contrib/osmo-pcap-server-tls.cfg b/contrib/osmo-pcap-server-tls.cfg new file mode 100644 index 0000000..52f66f5 --- /dev/null +++ b/contrib/osmo-pcap-server-tls.cfg @@ -0,0 +1,27 @@ +! +! OsmoPCAPServer (UNKNOWN) configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level all everything + logging level pcap notice + logging level client notice + logging level server notice + logging level vty notice +! +line vty + no login +! +server + base-path /tmp + server ip 127.0.0.1 + server port 6001 + max-file-size 262144000 + client zecke1 127.0.0.1 + client zecke2 127.0.0.1 store tls + client zecke3 127.0.0.2 no-store tls + client zecke4 127.0.0.3 no-store + enable tls + tls priority SECURE diff --git a/doc/tls.txt b/doc/tls.txt new file mode 100644 index 0000000..d2015f0 --- /dev/null +++ b/doc/tls.txt @@ -0,0 +1,76 @@ +TLS support +=========== + +Protect forwarded PCAP packet against eave-dropping by using +TLS between client and server. + +Anonymous TLS +^^^^^^^^^^^^^ + +The minimal configuration will use TLS with perfect forward +secrecy but not use X509 certificates. This means a client +will not know if it connects to the intended server but an +attacker listening will not be able to determine the content +of the messages. + +Client:: +--- + enable tls + tls dh generate + tls priority NORMAL:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:+ANON-ECDH:+ANON-DH +---- + +Server:: +---- + enable tls + tls dh generate + tls allow-auth anonymous +---- + + +Authenticate Server +^^^^^^^^^^^^^^^^^^^ + +This will use x509 certificates and allows a client to verify +it connects to a server with the right credentials. This will +protect messages against eaves-dropping and sending data to the +wrong system. + + + +Client:: + +---- + enable tls + tls verify-cert + tls capath /etc/osmocom/ca.pem +---- + +Server:: + +---- + enable tls + tls allow-auth x509 + tls capath /etc/osmocom/ca.pem + tls crlfile /etc/osmocom/server.crl + tls server-cert /etc/osmocom/server.crt + tls server-key /etc/osmosomc/server.key + client NAME IP store tls +---- + +Client certificate +^^^^^^^^^^^^^^^^^^ + +Currently this is not implemented. In the future a client +can be authenticated based on the SN/CN of a certificate. + +Debugging +========= + +GNUtls debugging can be enabled by setting the TLS debug +region to debug and then setting the _tls loglevel N_. The +setting will be applied on the next connection using TLS. + +---- + logging level tls debug + tls loglevel 9 diff --git a/include/osmo-pcap/Makefile.am b/include/osmo-pcap/Makefile.am index 1a446bc..b71e70c 100644 --- a/include/osmo-pcap/Makefile.am +++ b/include/osmo-pcap/Makefile.am @@ -1 +1 @@ -noinst_HEADERS = common.h osmo_pcap_client.h osmo_pcap_server.h wireformat.h +noinst_HEADERS = common.h osmo_pcap_client.h osmo_pcap_server.h wireformat.h osmo_tls.h diff --git a/include/osmo-pcap/common.h b/include/osmo-pcap/common.h index b8f8110..fff452f 100644 --- a/include/osmo-pcap/common.h +++ b/include/osmo-pcap/common.h @@ -34,6 +34,7 @@ enum { DCLIENT, DSERVER, DVTY, + DTLS, Debug_LastEntry, }; diff --git a/include/osmo-pcap/osmo_pcap_client.h b/include/osmo-pcap/osmo_pcap_client.h index ee81e50..b8ceb38 100644 --- a/include/osmo-pcap/osmo_pcap_client.h +++ b/include/osmo-pcap/osmo_pcap_client.h @@ -20,6 +20,8 @@ * */ +#include "osmo_tls.h" + #include <inttypes.h> #include <pcap.h> @@ -64,6 +66,20 @@ struct osmo_pcap_client { struct osmo_wqueue wqueue; struct osmo_timer_list timer; + /* TLS handling */ + bool tls_on; + bool tls_verify; + char *tls_hostname; + char *tls_capath; + char *tls_priority; + + char *tls_client_cert; + char *tls_client_key; + + unsigned tls_log_level; + + struct osmo_tls_session tls_session; + /* statistics */ struct rate_ctr_group *ctrg; }; @@ -79,3 +95,5 @@ void osmo_client_send_data(struct osmo_pcap_client *client, struct pcap_pkthdr *hdr, const uint8_t *data); void osmo_client_send_link(struct osmo_pcap_client *client); void osmo_client_connect(struct osmo_pcap_client *); + +void osmo_client_reconnect(struct osmo_pcap_client *); diff --git a/include/osmo-pcap/osmo_pcap_server.h b/include/osmo-pcap/osmo_pcap_server.h index a386a2a..c1d318e 100644 --- a/include/osmo-pcap/osmo_pcap_server.h +++ b/include/osmo-pcap/osmo_pcap_server.h @@ -24,9 +24,11 @@ #define OSMO_PCAP_SERVER_H #include "wireformat.h" +#include "osmo_tls.h" #include <osmocom/core/select.h> #include <osmocom/core/linuxlist.h> +#include <osmocom/core/write_queue.h> #include <sys/socket.h> #include <netinet/in.h> @@ -34,6 +36,7 @@ #include <pcap.h> +#include <stdbool.h> #include <time.h> struct rate_ctr_group; @@ -74,7 +77,7 @@ struct osmo_pcap_conn { struct in_addr remote_addr; /* Remote connection */ - struct osmo_fd rem_fd; + struct osmo_wqueue rem_wq; int local_fd; char *curr_filename; @@ -93,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 { @@ -108,6 +117,20 @@ struct osmo_pcap_server { void *zmq_ctx; void *zmq_publ; + /* tls base */ + bool tls_on; + bool tls_allow_anon; + bool tls_allow_x509; + unsigned tls_log_level; + char *tls_priority; + char *tls_capath; + char *tls_crlfile; + char *tls_server_cert; + char *tls_server_key; + char *tls_dh_pkcs3; + gnutls_dh_params_t dh_params; + bool dh_params_allocated; + char *base_path; off_t max_size; @@ -125,5 +148,6 @@ struct osmo_pcap_conn *osmo_pcap_server_find(struct osmo_pcap_server *ser, void osmo_pcap_server_delete(struct osmo_pcap_conn *conn); void vty_server_init(struct osmo_pcap_server *server); void osmo_pcap_server_close_trace(struct osmo_pcap_conn *conn); +void osmo_pcap_server_close_conn(struct osmo_pcap_conn *conn); #endif diff --git a/include/osmo-pcap/osmo_tls.h b/include/osmo-pcap/osmo_tls.h new file mode 100644 index 0000000..0637739 --- /dev/null +++ b/include/osmo-pcap/osmo_tls.h @@ -0,0 +1,80 @@ +/* + * osmo-pcap TLS code + * + * (C) 2016 by Holger Hans Peter Freyther <holger@moiji-mobile.com> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include <gnutls/gnutls.h> +#include <gnutls/abstract.h> + +#include <stdbool.h> +#include <stdint.h> + +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; + bool need_handshake; + bool need_resend; + gnutls_session_t 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; + gnutls_certificate_credentials_t cert_cred; + + /* the private certificate */ + bool pcert_alloc; + gnutls_pcert_st pcert; + + /* the private key in _RAM_ */ + bool privk_alloc; + gnutls_privkey_t privk; + + 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); +}; + +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); +void osmo_tls_server_init(struct osmo_pcap_server *server); + +void osmo_tls_dh_load(struct osmo_pcap_server *server); +void osmo_tls_dh_generate(struct osmo_pcap_server *server); diff --git a/osmoappdesc.py b/osmoappdesc.py index fce58fb..39928eb 100644 --- a/osmoappdesc.py +++ b/osmoappdesc.py @@ -15,8 +15,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. app_configs = { - "osmo-pcap-client": ["contrib/osmo-pcap-client.cfg"], - "osmo-pcap-server": ["contrib/osmo-pcap-server.cfg"] + "osmo-pcap-client": ["contrib/osmo-pcap-client.cfg", "contrib/osmo-pcap-client-tls.cfg"], + "osmo-pcap-server": ["contrib/osmo-pcap-server.cfg", "contrib/osmo-pcap-server-tls.cfg"] } apps = [ diff --git a/src/Makefile.am b/src/Makefile.am index 9674cdb..0532acf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,13 +1,16 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/ -AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(PCAP_CFLAGS) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(PCAP_CFLAGS) $(LIBGNUTLS_CFLAGS) bin_PROGRAMS = osmo_pcap_client osmo_pcap_server osmo_pcap_client_SOURCES = osmo_client_main.c osmo_common.c \ osmo_client_core.c osmo_client_vty.c \ - osmo_client_network.c -osmo_pcap_client_LDADD = $(PCAP_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOGSM_LIBS) + osmo_client_network.c osmo_tls.c +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_client_main.c b/src/osmo_client_main.c index f5ba41a..0bbeb7a 100644 --- a/src/osmo_client_main.c +++ b/src/osmo_client_main.c @@ -22,6 +22,7 @@ #include <osmo-pcap/common.h> #include <osmo-pcap/osmo_pcap_client.h> +#include <osmo-pcap/osmo_tls.h> #include <osmocom/core/application.h> #include <osmocom/core/rate_ctr.h> @@ -203,6 +204,8 @@ int main(int argc, char **argv) signal(SIGUSR1, &signal_handler); osmo_init_ignore_signals(); + osmo_tls_init(); + rc = telnet_init(tall_bsc_ctx, NULL, 4240); if (rc < 0) { LOGP(DCLIENT, LOGL_ERROR, "Failed to bind telnet interface\n"); @@ -215,6 +218,7 @@ int main(int argc, char **argv) exit(1); } pcap_client->fd.fd = -1; + pcap_client->tls_verify = true; vty_client_init(pcap_client); /* initialize the queue */ diff --git a/src/osmo_client_network.c b/src/osmo_client_network.c index 2400f3a..1bd5898 100644 --- a/src/osmo_client_network.c +++ b/src/osmo_client_network.c @@ -47,6 +47,7 @@ static void _osmo_client_connect(void *_data) static void lost_connection(struct osmo_pcap_client *client) { if (client->wqueue.bfd.fd >= 0) { + osmo_tls_release(&client->tls_session); osmo_fd_unregister(&client->wqueue.bfd); close(client->wqueue.bfd.fd); client->wqueue.bfd.fd = -1; @@ -100,6 +101,22 @@ static int write_cb(struct osmo_fd *fd, struct msgb *msg) return 0; } +static void handshake_done_cb(struct osmo_tls_session *session) +{ + struct osmo_pcap_client *client; + + client = container_of(session, struct osmo_pcap_client, tls_session); + osmo_client_send_link(client); +} + +static void tls_error_cb(struct osmo_tls_session *session) +{ + struct osmo_pcap_client *client; + + client = container_of(session, struct osmo_pcap_client, tls_session); + lost_connection(client); +} + void osmo_client_send_data(struct osmo_pcap_client *client, struct pcap_pkthdr *in_hdr, const uint8_t *data) { @@ -177,7 +194,6 @@ void osmo_client_connect(struct osmo_pcap_client *client) client->wqueue.read_cb = read_cb; client->wqueue.write_cb = write_cb; client->wqueue.bfd.when = BSC_FD_READ; - client->wqueue.bfd.data = client; osmo_wqueue_clear(&client->wqueue); fd = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, @@ -199,5 +215,26 @@ void osmo_client_connect(struct osmo_pcap_client *client) } rate_ctr_inc(&client->ctrg->ctr[CLIENT_CTR_CONNECT]); - osmo_client_send_link(client); + + /* + * The write queue needs to work differently for GNUtls. Before we can + * send data we will need to complete handshake. + */ + if (client->tls_on) { + if (!osmo_tls_init_client_session(client)) { + lost_connection(client); + return; + } + client->tls_session.handshake_done = handshake_done_cb; + client->tls_session.error = tls_error_cb; + } else { + client->wqueue.bfd.cb = osmo_wqueue_bfd_cb; + client->wqueue.bfd.data = client; + osmo_client_send_link(client); + } +} + +void osmo_client_reconnect(struct osmo_pcap_client *client) +{ + lost_connection(client); } diff --git a/src/osmo_client_vty.c b/src/osmo_client_vty.c index a8739b1..a409cf4 100644 --- a/src/osmo_client_vty.c +++ b/src/osmo_client_vty.c @@ -1,7 +1,7 @@ /* * osmo-pcap-client code * - * (C) 2011 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2011-2016 by Holger Hans Peter Freyther <holger@moiji-mobile.com> * (C) 2011 by On-Waves * All Rights Reserved * @@ -62,6 +62,26 @@ static int config_write_client(struct vty *vty) if (pcap_client->gprs_filtering) vty_out(vty, " pcap add-filter gprs%s", VTY_NEWLINE); + if (pcap_client->tls_on) { + vty_out(vty, " enable tls%s", VTY_NEWLINE); + vty_out(vty, " tls hostname %s%s", pcap_client->tls_hostname, VTY_NEWLINE); + vty_out(vty, " %stls verify-cert%s", + pcap_client->tls_verify ? "" : "no ", VTY_NEWLINE); + if (pcap_client->tls_capath) + vty_out(vty, " tls capath %s%s", pcap_client->tls_capath, VTY_NEWLINE); + if (pcap_client->tls_client_cert) + vty_out(vty, " tls client-cert %s%s", + pcap_client->tls_client_cert, VTY_NEWLINE); + if (pcap_client->tls_client_key) + vty_out(vty, " tls client-key %s%s", + pcap_client->tls_client_key, VTY_NEWLINE); + if (pcap_client->tls_priority) + vty_out(vty, " tls priority %s%s", + pcap_client->tls_priority, VTY_NEWLINE); + vty_out(vty, " tls log-level %d%s", + pcap_client->tls_log_level, VTY_NEWLINE); + } + if (pcap_client->srv_ip) vty_out(vty, " server ip %s%s", pcap_client->srv_ip, VTY_NEWLINE); @@ -131,6 +151,162 @@ DEFUN(cfg_client_loop, return CMD_SUCCESS; } + +#define TLS_STR "Transport Layer Security\n" + +DEFUN(cfg_enable_tls, + cfg_enable_tls_cmd, + "enable tls", + "Enable\n" "Transport Layer Security\n") +{ + if (!pcap_client->tls_on) { + if (pcap_client->wqueue.bfd.fd >= 0) + osmo_client_reconnect(pcap_client); + } + + pcap_client->tls_on = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_disable_tls, + cfg_disable_tls_cmd, + "disable tls", + "Disable\n" "Transport Layer Security\n") +{ + if (pcap_client->tls_on) + osmo_client_reconnect(pcap_client); + + pcap_client->tls_on = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_hostname, + cfg_tls_hostname_cmd, + "tls hostname NAME", + TLS_STR "hostname for certificate validation\n" "name\n") +{ + talloc_free(pcap_client->tls_hostname); + pcap_client->tls_hostname = talloc_strdup(pcap_client, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_hostname, + cfg_no_tls_hostname_cmd, + "no tls hostname", + NO_STR TLS_STR "hostname for certificate validation\n") +{ + talloc_free(pcap_client->tls_hostname); + pcap_client->tls_hostname = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_verify, + cfg_tls_verify_cmd, + "tls verify-cert", + TLS_STR "Verify certificates\n") +{ + pcap_client->tls_verify = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_verify, + cfg_no_tls_verify_cmd, + "no tls verify-cert", + NO_STR TLS_STR "Verify certificates\n") +{ + pcap_client->tls_verify = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_capath, + cfg_tls_capath_cmd, + "tls capath .PATH", + TLS_STR "Trusted root certificates\n" "Filename\n") +{ + talloc_free(pcap_client->tls_capath); + pcap_client->tls_capath = talloc_strdup(pcap_client, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_capath, + cfg_no_tls_capath_cmd, + "no tls capath", + NO_STR TLS_STR "Trusted root certificates\n") +{ + talloc_free(pcap_client->tls_capath); + pcap_client->tls_capath = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_client_cert, + cfg_tls_client_cert_cmd, + "tls client-cert .PATH", + TLS_STR "Client certificate for authentication\n" "Filename\n") +{ + talloc_free(pcap_client->tls_client_cert); + pcap_client->tls_client_cert = talloc_strdup(pcap_client, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_client_cert, + cfg_no_tls_client_cert_cmd, + "no tls client-cert", + NO_STR TLS_STR "Client certificate for authentication\n") +{ + talloc_free(pcap_client->tls_client_cert); + pcap_client->tls_client_cert = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_client_key, + cfg_tls_client_key_cmd, + "tls client-key .PATH", + TLS_STR "Client private key\n" "Filename\n") +{ + talloc_free(pcap_client->tls_client_key); + pcap_client->tls_client_key = talloc_strdup(pcap_client, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_client_key, + cfg_no_tls_client_key_cmd, + "no tls client-key", + NO_STR TLS_STR "Client private key\n") +{ + talloc_free(pcap_client->tls_client_key); + pcap_client->tls_client_key = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_priority, + cfg_tls_priority_cmd, + "tls priority STR", + TLS_STR "Priority string for GNUtls\n" "Priority string\n") +{ + talloc_free(pcap_client->tls_priority); + pcap_client->tls_priority = talloc_strdup(pcap_client, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_priority, + cfg_no_tls_priority_cmd, + "no tls priority", + NO_STR TLS_STR "Priority string for GNUtls\n") +{ + talloc_free(pcap_client->tls_priority); + pcap_client->tls_priority = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_log_level, + cfg_tls_log_level_cmd, + "tls log-level <0-255>", + TLS_STR "Log-level\n" "GNUtls debug level\n") +{ + pcap_client->tls_log_level = atoi(argv[0]); + return CMD_SUCCESS; +} + DEFUN(cfg_server_ip, cfg_server_ip_cmd, "server ip A.B.C.D", @@ -164,6 +340,22 @@ int vty_client_init(struct osmo_pcap_client *pcap) install_element(CLIENT_NODE, &cfg_server_ip_cmd); install_element(CLIENT_NODE, &cfg_server_port_cmd); + install_element(CLIENT_NODE, &cfg_enable_tls_cmd); + install_element(CLIENT_NODE, &cfg_disable_tls_cmd); + install_element(CLIENT_NODE, &cfg_tls_hostname_cmd); + install_element(CLIENT_NODE, &cfg_no_tls_hostname_cmd); + install_element(CLIENT_NODE, &cfg_tls_verify_cmd); + install_element(CLIENT_NODE, &cfg_no_tls_verify_cmd); + install_element(CLIENT_NODE, &cfg_tls_capath_cmd); + install_element(CLIENT_NODE, &cfg_no_tls_capath_cmd); + install_element(CLIENT_NODE, &cfg_tls_client_cert_cmd); + install_element(CLIENT_NODE, &cfg_no_tls_client_cert_cmd); + install_element(CLIENT_NODE, &cfg_tls_client_key_cmd); + install_element(CLIENT_NODE, &cfg_no_tls_client_key_cmd); + install_element(CLIENT_NODE, &cfg_tls_priority_cmd); + install_element(CLIENT_NODE, &cfg_no_tls_priority_cmd); + install_element(CLIENT_NODE, &cfg_tls_log_level_cmd); + install_element(CLIENT_NODE, &cfg_client_add_gprs_cmd); install_element(CLIENT_NODE, &cfg_client_del_gprs_cmd); diff --git a/src/osmo_common.c b/src/osmo_common.c index 33ec1b2..bb7d011 100644 --- a/src/osmo_common.c +++ b/src/osmo_common.c @@ -49,6 +49,12 @@ static const struct log_info_cat default_categories[] = { .color = "\033[1;34m", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DTLS] = { + .name = "DTLS", + .description = "TLS code", + .color = "\033[1;34m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; const struct log_info log_info = { diff --git a/src/osmo_server_main.c b/src/osmo_server_main.c index bb94ec4..37a9632 100644 --- a/src/osmo_server_main.c +++ b/src/osmo_server_main.c @@ -22,6 +22,7 @@ #include <osmo-pcap/common.h> #include <osmo-pcap/osmo_pcap_server.h> +#include <osmo-pcap/osmo_tls.h> #include <osmocom/core/application.h> #include <osmocom/core/rate_ctr.h> @@ -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"); @@ -244,6 +247,8 @@ int main(int argc, char **argv) exit(1); } + osmo_tls_server_init(pcap_server); + /* attempt to connect to the remote */ if (osmo_pcap_server_listen(pcap_server) != 0) { LOGP(DSERVER, LOGL_ERROR, diff --git a/src/osmo_server_network.c b/src/osmo_server_network.c index 8d41eaf..a854223 100644 --- a/src/osmo_server_network.c +++ b/src/osmo_server_network.c @@ -1,7 +1,7 @@ /* * osmo-pcap-server code * - * (C) 2011-2017 by Holger Hans Peter Freyther <holger@moiji-mobile.com> + * (C) 2011-2016 by Holger Hans Peter Freyther <holger@moiji-mobile.com> * (C) 2011 by On-Waves * All Rights Reserved * @@ -129,16 +129,22 @@ void osmo_pcap_server_close_trace(struct osmo_pcap_conn *conn) static void close_connection(struct osmo_pcap_conn *conn) { - if (conn->rem_fd.fd >= 0) { - close(conn->rem_fd.fd); - conn->rem_fd.fd = -1; - osmo_fd_unregister(&conn->rem_fd); + 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); } osmo_pcap_server_close_trace(conn); client_event(conn, "disconnect", NULL); } +void osmo_pcap_server_close_conn(struct osmo_pcap_conn *conn) +{ + return close_connection(conn); +} + static void restart_pcap(struct osmo_pcap_conn *conn) { time_t now = time(NULL); @@ -182,14 +188,13 @@ static void restart_pcap(struct osmo_pcap_conn *conn) conn->last_write = *tm; } -static void link_data(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data) +static int link_data(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data) { struct pcap_file_header *hdr; if (data->len != sizeof(*hdr)) { LOGP(DSERVER, LOGL_ERROR, "The pcap_file_header does not fit.\n"); - close_connection(conn); - return; + return -1; } hdr = (struct pcap_file_header *) &data->data[0]; @@ -200,12 +205,14 @@ static void link_data(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data) conn->file_hdr = *hdr; restart_pcap(conn); } + + return 1; } /* * Check if we are past the limit or on a day change */ -static void write_data(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data) +static int write_data(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data) { time_t now = time(NULL); struct tm *tm = localtime(&now); @@ -215,13 +222,12 @@ static void write_data(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data) if (conn->no_store) { conn->last_write = *tm; - return; + return 1; } if (conn->local_fd < -1) { LOGP(DSERVER, LOGL_ERROR, "No file is open. close connection.\n"); - close_connection(conn); - return; + return -1; } off_t cur = lseek(conn->local_fd, 0, SEEK_CUR); @@ -239,8 +245,9 @@ static void write_data(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data) rc = write(conn->local_fd, &data->data[0], data->len); if (rc != data->len) { LOGP(DSERVER, LOGL_ERROR, "Failed to write for %s\n", conn->name); - close_connection(conn); + return -1; } + return 1; } @@ -303,7 +310,9 @@ struct osmo_pcap_conn *osmo_pcap_server_find(struct osmo_pcap_server *server, conn->name = talloc_strdup(conn, name); - conn->rem_fd.fd = -1; + /* we never write */ + osmo_wqueue_init(&conn->rem_wq, 0); + conn->rem_wq.bfd.fd = -1; conn->local_fd = -1; conn->server = server; conn->data = (struct osmo_pcap_data *) &conn->buf[0]; @@ -311,14 +320,29 @@ struct osmo_pcap_conn *osmo_pcap_server_find(struct osmo_pcap_server *server, return conn; } -static int read_cb_initial(struct osmo_fd *fd, struct osmo_pcap_conn *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) +{ + 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) { int rc; - rc = read(fd->fd, &conn->buf[sizeof(*conn->data) - conn->pend], conn->pend); + + rc = do_read(conn, &conn->buf[sizeof(*conn->data) - conn->pend], conn->pend); if (rc <= 0) { LOGP(DSERVER, LOGL_ERROR, "Too short packet. Got %d, wanted %d\n", rc, conn->data->len); - close_connection(conn); return -1; } @@ -326,7 +350,6 @@ static int read_cb_initial(struct osmo_fd *fd, struct osmo_pcap_conn *conn) if (conn->pend < 0) { LOGP(DSERVER, LOGL_ERROR, "Someone got the pending read wrong: %d\n", conn->pend); - close_connection(conn); return -1; } else if (conn->pend == 0) { conn->data->len = ntohs(conn->data->len); @@ -334,7 +357,6 @@ static int read_cb_initial(struct osmo_fd *fd, struct osmo_pcap_conn *conn) if (conn->data->len > SERVER_MAX_DATA_SIZE) { LOGP(DSERVER, LOGL_ERROR, "Implausible data length: %u\n", conn->data->len); - close_connection(conn); return -1; } @@ -342,17 +364,17 @@ static int read_cb_initial(struct osmo_fd *fd, struct osmo_pcap_conn *conn) conn->pend = conn->data->len; } - return 0; + return 1; } -static int read_cb_data(struct osmo_fd *fd, struct osmo_pcap_conn *conn) +static int read_cb_data(struct osmo_pcap_conn *conn) { int rc; - rc = read(fd->fd, &conn->data->data[conn->data->len - conn->pend], conn->pend); + + rc = do_read(conn, &conn->data->data[conn->data->len - conn->pend], conn->pend); if (rc <= 0) { LOGP(DSERVER, LOGL_ERROR, "Too short packet. Got %d, wanted %d\n", rc, conn->data->len); - close_connection(conn); return -1; } @@ -360,7 +382,6 @@ static int read_cb_data(struct osmo_fd *fd, struct osmo_pcap_conn *conn) if (conn->pend < 0) { LOGP(DSERVER, LOGL_ERROR, "Someone got the pending read wrong: %d\n", conn->pend); - close_connection(conn); return -1; } else if (conn->pend == 0) { conn->state = STATE_INITIAL; @@ -376,58 +397,121 @@ static int read_cb_data(struct osmo_fd *fd, struct osmo_pcap_conn *conn) switch (conn->data->type) { case PKT_LINK_HDR: - link_data(conn, conn->data); + return link_data(conn, conn->data); break; case PKT_LINK_DATA: - write_data(conn, conn->data); + return write_data(conn, conn->data); break; } } - return 0; + return 1; } -static int read_cb(struct osmo_fd *fd, unsigned int what) +static int dispatch_read(struct osmo_pcap_conn *conn) { - struct osmo_pcap_conn *conn; - - conn = fd->data; - if (conn->state == STATE_INITIAL) { if (conn->reopen) { LOGP(DSERVER, LOGL_INFO, "Reopening log for %s now.\n", conn->name); restart_pcap(conn); conn->reopen = 0; } - return read_cb_initial(fd, conn); + return read_cb_initial(conn); } else if (conn->state == STATE_DATA) { - return read_cb_data(fd, conn); + return read_cb_data(conn); } return 0; } +static int read_cb(struct osmo_fd *fd) +{ + struct osmo_pcap_conn *conn; + int rc; + + conn = fd->data; + rc = dispatch_read(conn); + if (rc <= 0) + close_connection(conn); + 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) { close_connection(client); memset(&client->file_hdr, 0, sizeof(client->file_hdr)); - client->rem_fd.fd = new_fd; - if (osmo_fd_register(&client->rem_fd) != 0) { + client->rem_wq.bfd.fd = new_fd; + if (osmo_fd_register(&client->rem_wq.bfd) != 0) { LOGP(DSERVER, LOGL_ERROR, "Failed to register fd.\n"); - client->rem_fd.fd = -1; + client->rem_wq.bfd.fd = -1; close(new_fd); return; } rate_ctr_inc(&client->ctrg->ctr[PEER_CTR_CONNECT]); - client->rem_fd.data = client; - client->rem_fd.when = BSC_FD_READ; - client->rem_fd.cb = read_cb; client->state = STATE_INITIAL; client->pend = sizeof(*client->data); + + if (client->tls_use && !server->tls_on) { + LOGP(DSERVER, LOGL_NOTICE, + "Require TLS but not enabled on conn=%s\n", + client->name); + close_connection(client); + return; + } else 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) @@ -460,6 +544,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_server_vty.c b/src/osmo_server_vty.c index d13ea6f..b2919ae 100644 --- a/src/osmo_server_vty.c +++ b/src/osmo_server_vty.c @@ -41,6 +41,45 @@ static struct cmd_node server_node = { 1, }; +static void write_tls(struct vty *vty, struct osmo_pcap_server *pcap_server) +{ + if (!pcap_server->tls_on) + return; + + vty_out(vty, " enable tls%s", VTY_NEWLINE); + vty_out(vty, " tls log-level %d%s", + pcap_server->tls_log_level, VTY_NEWLINE); + + if (pcap_server->tls_allow_anon) + vty_out(vty, " tls allow-auth anonymous%s", VTY_NEWLINE); + + if (pcap_server->tls_allow_x509) + vty_out(vty, " tls allow-auth x509%s", VTY_NEWLINE); + + if (pcap_server->tls_priority) + vty_out(vty, " tls priority %s%s", + pcap_server->tls_priority, VTY_NEWLINE); + if (pcap_server->tls_capath) + vty_out(vty, " tls capath %s%s", pcap_server->tls_capath, VTY_NEWLINE); + + if (pcap_server->tls_crlfile) + vty_out(vty, " tls crlfile %s%s", pcap_server->tls_crlfile, VTY_NEWLINE); + + if (pcap_server->tls_server_cert) + vty_out(vty, " tls server-cert %s%s", + pcap_server->tls_server_cert, VTY_NEWLINE); + + if (pcap_server->tls_server_key) + vty_out(vty, " tls server-key %s%s", + pcap_server->tls_server_key, VTY_NEWLINE); + + if (pcap_server->tls_dh_pkcs3) + vty_out(vty, " tls dh pkcs3 %s%s", + pcap_server->tls_dh_pkcs3, VTY_NEWLINE); + else + vty_out(vty, " tls dh generate%s", VTY_NEWLINE); +} + static int config_write_server(struct vty *vty) { struct osmo_pcap_conn *conn; @@ -59,10 +98,13 @@ static int config_write_server(struct vty *vty) vty_out(vty, " zeromq-publisher %s %d%s", pcap_server->zmq_ip, pcap_server->zmq_port, VTY_NEWLINE); + write_tls(vty, pcap_server); + llist_for_each_entry(conn, &pcap_server->conn, entry) { - vty_out(vty, " client %s %s%s%s", + vty_out(vty, " client %s %s%s%s%s", conn->name, conn->remote_host, - conn->no_store ? " no-store" : "", + conn->no_store ? " no-store" : " store", + conn->tls_use ? " tls" : "", VTY_NEWLINE); } @@ -116,32 +158,62 @@ DEFUN(cfg_server_max_size, return CMD_SUCCESS; } -DEFUN(cfg_server_client, - cfg_server_client_cmd, - "client NAME A.B.C.D [no-store]", - CLIENT_STR "Remote name used in filenames\n" "IP of the remote\n" "Do not store traffic\n") +static int manage_client(struct osmo_pcap_server *pcap_server, + struct vty *vty, + const char *name, const char *remote_host, + bool no_store, bool use_tls) { struct osmo_pcap_conn *conn; - conn = osmo_pcap_server_find(pcap_server, argv[0]); + conn = osmo_pcap_server_find(pcap_server, name); if (!conn) { vty_out(vty, "Failed to create a pcap server.\n"); return CMD_WARNING; } talloc_free(conn->remote_host); - conn->remote_host = talloc_strdup(pcap_server, argv[1]); - inet_aton(argv[1], &conn->remote_addr); + conn->remote_host = talloc_strdup(pcap_server, remote_host); + inet_aton(remote_host, &conn->remote_addr); /* Checking no-store and maybe closing a pcap file */ - if (argc >= 3) { + if (no_store) { osmo_pcap_server_close_trace(conn); conn->no_store = 1; } else conn->no_store = 0; + if (use_tls) { + /* force moving to TLS */ + if (!conn->tls_use) + osmo_pcap_server_close_conn(conn); + conn->tls_use = true; + } else { + conn->tls_use = false; + } + return CMD_SUCCESS; } + +DEFUN(cfg_server_client, + cfg_server_client_cmd, + "client NAME A.B.C.D [no-store] [tls]", + CLIENT_STR "Remote name used in filenames\n" + "IP of the remote\n" "Do not store traffic\n" + "Use Transport Level Security\n") +{ + return manage_client(pcap_server, vty, argv[0], argv[1], argc >= 3, argc >= 4); +} + +DEFUN(cfg_server_client_store_tls, + cfg_server_client_store_tls_cmd, + "client NAME A.B.C.D store [tls]", + CLIENT_STR "Remote name used in filenames\n" + "IP of the remote\n" "Do not store traffic\n" + "Use Transport Level Security\n") +{ + return manage_client(pcap_server, vty, argv[0], argv[1], false, argc >= 3); +} + DEFUN(cfg_server_no_client, cfg_server_no_client_cmd, "no client NAME", @@ -241,6 +313,195 @@ DEFUN(cfg_no_server_zmq_ip_port, return CMD_SUCCESS; } +#define TLS_STR "Transport Layer Security\n" + +DEFUN(cfg_enable_tls, + cfg_enable_tls_cmd, + "enable tls", + "Enable\n" "Transport Layer Security\n") +{ + pcap_server->tls_on = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_disable_tls, + cfg_disable_tls_cmd, + "disable tls", + "Disable\n" "Transport Layer Security\n") +{ + pcap_server->tls_on = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_log_level, + cfg_tls_log_level_cmd, + "tls log-level <0-255>", + TLS_STR "Log-level\n" "GNUtls debug level\n") +{ + pcap_server->tls_log_level = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_allow_anon, + cfg_tls_allow_anon_cmd, + "tls allow-auth anonymous", + TLS_STR "allow authentication\n" "for anonymous\n") +{ + pcap_server->tls_allow_anon = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_allow_anon, + cfg_no_tls_allow_anon_cmd, + "no tls allow-auth anonymous", + NO_STR TLS_STR "allow authentication\n" "for anonymous\n") +{ + pcap_server->tls_allow_anon = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_allow_x509, + cfg_tls_allow_x509_cmd, + "tls allow-auth x509", + TLS_STR "allow authentication\n" "for certificates\n") +{ + pcap_server->tls_allow_x509 = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_allow_x509, + cfg_no_tls_allow_x509_cmd, + "no tls allow-auth x509", + NO_STR TLS_STR "allow authentication\n" "for certificates\n") +{ + pcap_server->tls_allow_x509 = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_priority, + cfg_tls_priority_cmd, + "tls priority STR", + TLS_STR "Priority string for GNUtls\n" "Priority string\n") +{ + talloc_free(pcap_server->tls_priority); + pcap_server->tls_priority = talloc_strdup(pcap_server, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_priority, + cfg_no_tls_priority_cmd, + "no tls priority", + NO_STR TLS_STR "Priority string for GNUtls\n") +{ + talloc_free(pcap_server->tls_priority); + pcap_server->tls_priority = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_capath, + cfg_tls_capath_cmd, + "tls capath .PATH", + TLS_STR "Trusted root certificates\n" "Filename\n") +{ + talloc_free(pcap_server->tls_capath); + pcap_server->tls_capath = talloc_strdup(pcap_server, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_capath, + cfg_no_tls_capath_cmd, + "no tls capath", + NO_STR TLS_STR "Trusted root certificates\n") +{ + talloc_free(pcap_server->tls_capath); + pcap_server->tls_capath = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_crlfile, + cfg_tls_crlfile_cmd, + "tls crlfile .PATH", + TLS_STR "CRL file\n" "Filename\n") +{ + talloc_free(pcap_server->tls_crlfile); + pcap_server->tls_crlfile = talloc_strdup(pcap_server, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_crlfile, + cfg_no_tls_crlfile_cmd, + "no tls crlfile", + NO_STR TLS_STR "CRL file\n") +{ + talloc_free(pcap_server->tls_crlfile); + pcap_server->tls_crlfile = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_server_cert, + cfg_tls_server_cert_cmd, + "tls server-cert .PATH", + TLS_STR "Server certificate\n" "Filename\n") +{ + talloc_free(pcap_server->tls_server_cert); + pcap_server->tls_server_cert = talloc_strdup(pcap_server, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_server_cert, + cfg_no_tls_server_cert_cmd, + "no tls server-cert", + NO_STR TLS_STR "Server certificate\n") +{ + talloc_free(pcap_server->tls_server_cert); + pcap_server->tls_server_cert = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_server_key, + cfg_tls_server_key_cmd, + "tls server-key .PATH", + TLS_STR "Server private key\n" "Filename\n") +{ + talloc_free(pcap_server->tls_server_key); + pcap_server->tls_server_key = talloc_strdup(pcap_server, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_tls_server_key, + cfg_no_tls_server_key_cmd, + "no tls server-key", + NO_STR TLS_STR "Server private key\n") +{ + talloc_free(pcap_server->tls_server_key); + pcap_server->tls_server_key = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_dh_pkcs3, + cfg_tls_dh_pkcs3_cmd, + "tls dh pkcs .FILE", + TLS_STR "Diffie-Hellman Key Exchange\n" "PKCS3\n" "Filename\n") +{ + talloc_free(pcap_server->tls_dh_pkcs3); + pcap_server->tls_dh_pkcs3 = talloc_strdup(pcap_server, argv[0]); + + osmo_tls_dh_load(pcap_server); + return CMD_SUCCESS; +} + +DEFUN(cfg_tls_dh_generate, + cfg_tls_dh_generate_cmd, + "tls dh generate", + TLS_STR "Diffie-Hellman Key Exchange\n" "Generate prime\n") +{ + talloc_free(pcap_server->tls_dh_pkcs3); + pcap_server->tls_dh_pkcs3 = NULL; + + osmo_tls_dh_generate(pcap_server); + return CMD_SUCCESS; +} + void vty_server_init(struct osmo_pcap_server *server) { install_element(CONFIG_NODE, &cfg_server_cmd); @@ -254,6 +515,28 @@ void vty_server_init(struct osmo_pcap_server *server) install_element(SERVER_NODE, &cfg_server_zmq_ip_port_cmd); install_element(SERVER_NODE, &cfg_no_server_zmq_ip_port_cmd); + /* tls for the server */ + install_element(SERVER_NODE, &cfg_enable_tls_cmd); + install_element(SERVER_NODE, &cfg_disable_tls_cmd); + install_element(SERVER_NODE, &cfg_tls_log_level_cmd); + install_element(SERVER_NODE, &cfg_tls_allow_anon_cmd); + install_element(SERVER_NODE, &cfg_no_tls_allow_anon_cmd); + install_element(SERVER_NODE, &cfg_tls_allow_x509_cmd); + install_element(SERVER_NODE, &cfg_no_tls_allow_x509_cmd); + install_element(SERVER_NODE, &cfg_tls_priority_cmd); + install_element(SERVER_NODE, &cfg_no_tls_priority_cmd); + install_element(SERVER_NODE, &cfg_tls_capath_cmd); + install_element(SERVER_NODE, &cfg_no_tls_capath_cmd); + install_element(SERVER_NODE, &cfg_tls_crlfile_cmd); + install_element(SERVER_NODE, &cfg_no_tls_crlfile_cmd); + install_element(SERVER_NODE, &cfg_tls_server_cert_cmd); + install_element(SERVER_NODE, &cfg_no_tls_server_cert_cmd); + install_element(SERVER_NODE, &cfg_tls_server_key_cmd); + install_element(SERVER_NODE, &cfg_no_tls_server_key_cmd); + install_element(SERVER_NODE, &cfg_tls_dh_generate_cmd); + install_element(SERVER_NODE, &cfg_tls_dh_pkcs3_cmd); + install_element(SERVER_NODE, &cfg_server_client_cmd); + install_element(SERVER_NODE, &cfg_server_client_store_tls_cmd); install_element(SERVER_NODE, &cfg_server_no_client_cmd); } diff --git a/src/osmo_tls.c b/src/osmo_tls.c new file mode 100644 index 0000000..aeecf6d --- /dev/null +++ b/src/osmo_tls.c @@ -0,0 +1,558 @@ +/* + * osmo-pcap TLS code + * + * (C) 2016 by Holger Hans Peter Freyther <holger@moiji-mobile.com> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmo-pcap/osmo_tls.h> +#include <osmo-pcap/osmo_pcap_client.h> +#include <osmo-pcap/osmo_pcap_server.h> +#include <osmo-pcap/common.h> + +#include <osmocom/core/write_queue.h> +#include <osmocom/core/talloc.h> + +#include <string.h> + +#define CHECK_RC(rc, str) \ + if (rc != 0) { \ + LOGP(DTLS, LOGL_ERROR, "%s with rc=%d\n", str, rc); \ + exit(1); \ + } + +static int generate_dh_params(struct osmo_pcap_server *server) +{ + int rc; + 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); + + /* allocate it */ + rc = gnutls_dh_params_init (&server->dh_params); + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "Failed to allocate DH params rc=%d\n", rc); + server->dh_params_allocated = false; + return rc; + } + + /* generate and check */ + rc = gnutls_dh_params_generate2 (server->dh_params, bits); + if (rc == GNUTLS_E_SUCCESS) + server->dh_params_allocated = true; + else { + LOGP(DTLS, LOGL_ERROR, "Failed to generate DH params rc=%d\n", rc); + server->dh_params_allocated = false; + gnutls_dh_params_deinit(server->dh_params); + } + return rc; +} + +void osmo_tls_dh_load(struct osmo_pcap_server *server) +{ + gnutls_datum_t data; + int rc; + + /* free it before we start */ + if (server->dh_params_allocated) { + gnutls_dh_params_deinit(server->dh_params); + server->dh_params_allocated = false; + } + /* check if we have all data */ + if (!server->tls_dh_pkcs3) { + LOGP(DTLS, LOGL_ERROR, "Can not generate missing pkcs3=%p\n", + server->tls_dh_pkcs3); + return; + } + /* initialize it again */ + rc = gnutls_dh_params_init (&server->dh_params); + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "Failed to allocate DH params rc=%d\n", rc); + server->dh_params_allocated = false; + return; + } + /* load prime and generator */ + rc = gnutls_load_file(server->tls_dh_pkcs3, &data); + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "Failed to load DH params from=%s rc=%d\n", + server->tls_dh_pkcs3, rc); + gnutls_dh_params_deinit(server->dh_params); + return; + } + rc = gnutls_dh_params_import_pkcs3(server->dh_params, &data, GNUTLS_X509_FMT_PEM); + gnutls_free(data.data); + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "Failed to import DH params rc=%d\n", rc); + gnutls_dh_params_deinit(server->dh_params); + return; + } + /* done */ + server->dh_params_allocated = true; +} + +void osmo_tls_dh_generate(struct osmo_pcap_server *server) +{ + if (server->dh_params_allocated) + gnutls_dh_params_deinit(server->dh_params); + generate_dh_params(server); +} + +static int cert_callback(gnutls_session_t tls_session, + const gnutls_datum_t * req_ca_rdn, int nreqs, + const gnutls_pk_algorithm_t * sign_algos, + int sign_algos_length, gnutls_pcert_st ** pcert, + unsigned int *pcert_length, gnutls_privkey_t * pkey) +{ + struct osmo_tls_session *sess = gnutls_session_get_ptr(tls_session); + gnutls_certificate_type_t type; + + LOGP(DTLS, LOGL_DEBUG, "cert callback from server\n"); + type = gnutls_certificate_type_get(tls_session); + if (type != GNUTLS_CRT_X509) + return -1; + + *pcert_length = 1; + *pcert = &sess->pcert; + *pkey = sess->privk; + return 0; +} + +static void tls_log_func(int level, const char *str) +{ + LOGP(DTLS, LOGL_DEBUG, "GNUtls: |<%d>| %s", level, str); +} + +static int verify_cert_cb(gnutls_session_t session) +{ + const char *hostname; + unsigned int status; + int ret; + + hostname = gnutls_session_get_ptr(session); + ret = gnutls_certificate_verify_peers3(session, + hostname, &status); + if (ret != 0) + return GNUTLS_E_CERTIFICATE_ERROR; + if (status != 0) + return GNUTLS_E_CERTIFICATE_ERROR; + return 0; +} + +static void release_keys(struct osmo_tls_session *sess) +{ + if (sess->pcert_alloc) { + gnutls_pcert_deinit(&sess->pcert); + sess->pcert_alloc = false; + } + if (sess->privk_alloc) { + gnutls_privkey_deinit(sess->privk); + sess->privk_alloc = false; + } +} + +void osmo_tls_init(void) +{ + int rc; + rc = gnutls_global_init(); + CHECK_RC(rc, "init failed"); + gnutls_global_set_log_function(tls_log_func); +} + +void osmo_tls_server_init(struct osmo_pcap_server *server) +{ + int rc; + + if (server->dh_params_allocated) + return; + rc = generate_dh_params(server); + CHECK_RC(rc, "dh params failed"); +} + +static int need_handshake(struct osmo_tls_session *tls_session) +{ + int rc; + + rc = gnutls_handshake(tls_session->session); + if (rc == 0) { + /* handshake is done. start writing if we are allowed to */ + LOGP(DTLS, LOGL_NOTICE, "TLS handshake done.\n"); + if (!llist_empty(&tls_session->wqueue->msg_queue)) + tls_session->wqueue->bfd.when = BSC_FD_WRITE | BSC_FD_READ; + else + tls_session->wqueue->bfd.when = BSC_FD_READ; + tls_session->need_handshake = false; + release_keys(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)) { + /* it failed for good.. */ + LOGP(DTLS, LOGL_ERROR, "handshake failed rc=%d str=%s\n", + rc, gnutls_strerror(rc)); + tls_session->wqueue->bfd.when = 0; + tls_session->error(tls_session); + } + return 0; +} + +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; +} + +static int tls_write(struct osmo_tls_session *sess) +{ + int rc; + sess->wqueue->bfd.when &= ~BSC_FD_WRITE; + + if (llist_empty(&sess->wqueue->msg_queue)) + return 0; + + if (sess->need_resend) { + rc = gnutls_record_send(sess->session, NULL, 0); + } else { + struct msgb *msg; + msg = (struct msgb *) sess->wqueue->msg_queue.next; + rc = gnutls_record_send(sess->session, msg->data, msg->len); + } + + if (rc > 0) { + sess->wqueue->current_length -= 1; + sess->need_resend = false; + struct msgb *msg = msgb_dequeue(&sess->wqueue->msg_queue); + msgb_free(msg); + } else if (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN) { + sess->need_resend = true; + } else if (gnutls_error_is_fatal(rc)) { + return rc; + } + + if (sess->need_resend || !llist_empty(&sess->wqueue->msg_queue)) + sess->wqueue->bfd.when |= BSC_FD_WRITE; + return rc; +} + +int osmo_tls_client_bfd_cb(struct osmo_fd *fd, unsigned what) +{ + struct osmo_tls_session *sess = fd->data; + + if (sess->need_handshake) + return need_handshake(sess); + + if (what & BSC_FD_READ) { + int rc = tls_read(sess); + if (rc <= 0) { + sess->error(sess); + return rc; + } + } + if (what & BSC_FD_WRITE) { + int rc = tls_write(sess); + if (rc < 0) { + sess->error(sess); + return rc; + } + } + + return 0; +} + +static int load_keys(struct osmo_pcap_client *client) +{ + struct osmo_tls_session *sess = &client->tls_session; + gnutls_datum_t data; + int rc; + + if (!client->tls_client_cert || !client->tls_client_key) { + LOGP(DTLS, LOGL_DEBUG, "Skipping x509 client cert %p %p\n", + client->tls_client_cert, client->tls_client_key); + return 0; + } + + + rc = gnutls_load_file(client->tls_client_cert, &data); + if (rc < 0) { + LOGP(DTLS, LOGL_ERROR, "Failed to load file=%s rc=%d\n", + client->tls_client_cert, rc); + return -1; + } + rc = gnutls_pcert_import_x509_raw(&sess->pcert, &data, GNUTLS_X509_FMT_PEM, 0); + gnutls_free(data.data); + if (rc < 0) { + LOGP(DTLS, LOGL_ERROR, "Failed to import file=%s rc=%d\n", + client->tls_client_cert, rc); + return -1; + } + sess->pcert_alloc = true; + + /* copied to RAM.. nothing we can do about it */ + rc = gnutls_load_file(client->tls_client_key, &data); + if (rc < 0) { + LOGP(DTLS, LOGL_ERROR, "Failed to load file=%s rc=%d\n", + client->tls_client_key, rc); + return -1; + } + gnutls_privkey_init(&sess->privk); + rc = gnutls_privkey_import_x509_raw(sess->privk, &data, GNUTLS_X509_FMT_PEM, NULL, 0); + gnutls_free(data.data); + if (rc < 0) { + LOGP(DTLS, LOGL_ERROR, "Failed to load file=%s rc=%d\n", + client->tls_client_key, rc); + release_keys(sess); + return -1; + } + sess->privk_alloc = true; + 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 */ + if (server->dh_params_allocated) { + gnutls_anon_set_server_dh_params(sess->anon_serv_cred, server->dh_params); + gnutls_certificate_set_dh_params(sess->cert_cred, server->dh_params); + } + + if (server->tls_allow_anon) + gnutls_credentials_set(sess->session, GNUTLS_CRD_ANON, sess->anon_serv_cred); + if (server->tls_allow_x509) + 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 (server->tls_crlfile) { + rc = gnutls_certificate_set_x509_crl_file( + sess->cert_cred, server->tls_crlfile, GNUTLS_X509_FMT_PEM); + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "Failed to load crlfile from path=%s rc=%d\n", + server->tls_crlfile, rc); + osmo_tls_release(sess); + return false; + } + } + + if (server->tls_server_cert && server->tls_server_key) { + rc = gnutls_certificate_set_x509_key_file( + sess->cert_cred, server->tls_server_cert, server->tls_server_key, + GNUTLS_X509_FMT_PEM); + if (rc != GNUTLS_E_SUCCESS) { + LOGP(DTLS, LOGL_ERROR, "Failed to load crt/key from path=%s/%s rc=%d\n", + server->tls_server_cert, server->tls_server_key, rc); + osmo_tls_release(sess); + return false; + } + } + + #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; + struct osmo_wqueue *wq = &client->wqueue; + unsigned int status; + int rc; + + gnutls_global_set_log_level(client->tls_log_level); + + memset(sess, 0, sizeof(*sess)); + sess->in_use = sess->anon_alloc = sess->cert_alloc = false; + rc = gnutls_init(&sess->session, GNUTLS_CLIENT | 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 (client->tls_priority) { + const char *err; + rc = gnutls_priority_set_direct(sess->session, client->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_client_credentials(&sess->anon_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_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 */ + gnutls_credentials_set(sess->session, GNUTLS_CRD_ANON, sess->anon_cred); + gnutls_credentials_set(sess->session, GNUTLS_CRD_CERTIFICATE, sess->cert_cred); + + if (client->tls_capath) { + rc = gnutls_certificate_set_x509_trust_file( + sess->cert_cred, client->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", + client->tls_capath, rc); + osmo_tls_release(sess); + return false; + } + } + + if (load_keys(client) != 0) { + osmo_tls_release(sess); + return false; + } + + gnutls_certificate_set_retrieve_function2(sess->cert_cred, cert_callback); + + /* set the hostname if we have one */ + if (client->tls_hostname) + gnutls_server_name_set(sess->session, GNUTLS_NAME_DNS, + client->tls_hostname, strlen(client->tls_hostname)); + + /* do the verification */ + if (client->tls_verify) { + gnutls_certificate_set_verify_function(sess->cert_cred, verify_cert_cb); + gnutls_certificate_verify_peers3(sess->session, client->tls_hostname, &status); + } else + LOGP(DTLS, LOGL_NOTICE, "Not going to validate certs as configured\n"); + + 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; +} + +void osmo_tls_release(struct osmo_tls_session *session) +{ + if (!session->in_use) + return; + + gnutls_deinit(session->session); + + release_keys(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; +} |