aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--configure.ac1
-rw-r--r--include/osmo-pcap/Makefile.am2
-rw-r--r--include/osmo-pcap/common.h1
-rw-r--r--include/osmo-pcap/osmo_pcap_client.h16
-rw-r--r--include/osmo-pcap/osmo_tls.h65
-rw-r--r--src/Makefile.am7
-rw-r--r--src/osmo_client_main.c4
-rw-r--r--src/osmo_client_network.c36
-rw-r--r--src/osmo_client_vty.c194
-rw-r--r--src/osmo_common.c6
-rw-r--r--src/osmo_tls.c351
12 files changed, 678 insertions, 7 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/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/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 4367e4c..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;
};
diff --git a/include/osmo-pcap/osmo_tls.h b/include/osmo-pcap/osmo_tls.h
new file mode 100644
index 0000000..bfc813e
--- /dev/null
+++ b/include/osmo-pcap/osmo_tls.h
@@ -0,0 +1,65 @@
+/*
+ * 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>
+
+struct osmo_fd;
+struct osmo_wqueue;
+struct osmo_pcap_client;
+
+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;
+
+ /* 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;
+
+ 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);
+void osmo_tls_release(struct osmo_tls_session *);
+
+int osmo_tls_client_bfd_cb(struct osmo_fd *fd, unsigned int what);
diff --git a/src/Makefile.am b/src/Makefile.am
index 9674cdb..83409db 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,12 +1,13 @@
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
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 e7fe82c..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,7 +215,23 @@ 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)
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_tls.c b/src/osmo_tls.c
new file mode 100644
index 0000000..ae957e6
--- /dev/null
+++ b/src/osmo_tls.c
@@ -0,0 +1,351 @@
+/*
+ * 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/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 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);
+}
+
+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);
+ 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;
+
+ 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;
+}
+
+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->cert_alloc)
+ gnutls_certificate_free_credentials(session->cert_cred);
+ session->in_use = false;
+}