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 #include @@ -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 + * 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 . + * + */ +#pragma once + +#include +#include + +#include + +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 #include +#include #include #include @@ -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 + * (C) 2011-2016 by Holger Hans Peter Freyther * (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 + * 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 . + * + */ + +#include +#include +#include + +#include +#include + +#include + +#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; +}