Hi all,
I'm Tobias and fond of using libtls.
I have a certain use case, where I want to do TLS/SSL but
can only work with buffers/callbacks and not sockets or FDs.
In p(l)ain openssl, this is doable, but not nice. Libtls
does not yet have such a facility.
I did a patch (or Pull-Request in GitHub parlance) against
portable on github, it would be great if it were considered.
Then I could migrate the SSL facilities of the Squeak
programming system from openssl to libtls.
Best regards
-Tobias
diff --git src/lib/libtls/Makefile src/lib/libtls/Makefile
index f51f2cd..f4252a6 100644
--- src/lib/libtls/Makefile
+++ src/lib/libtls/Makefile
@@ -13,6 +13,7 @@ LDADD+= -L${BSDOBJDIR}/lib/libssl/ssl -lssl
HDRS= tls.h
SRCS= tls.c \
+ tls_bio_cb.c \
tls_client.c \
tls_config.c \
tls_conninfo.c \
diff --git src/lib/libtls/tls.c src/lib/libtls/tls.c
index 22e8c87..7417e8b 100644
--- src/lib/libtls/tls.c
+++ src/lib/libtls/tls.c
@@ -393,6 +393,10 @@ tls_reset(struct tls *ctx)
tls_free_conninfo(ctx->conninfo);
free(ctx->conninfo);
ctx->conninfo = NULL;
+
+ ctx->cb_read = NULL;
+ ctx->cb_write = NULL;
+ ctx->cb_payload = NULL;
}
int
@@ -580,3 +584,58 @@ tls_close(struct tls *ctx)
errno = 0;
return (rv);
}
+
+static int
+tls_bio_cb_write(BIO *h, const char *buf, int num, void *payload)
+{
+ int ret = 0;
+ struct tls *ctx = (struct tls *)payload;
+ ret = (ctx->cb_write)(ctx, (const void*)buf, (size_t)num,
ctx->cb_payload);
+ return (ret);
+}
+
+static int
+tls_bio_cb_read(BIO *h, char *buf, int size, void *payload)
+{
+ int ret = 0;
+ struct tls *ctx = (struct tls *)payload;
+ ret = (ctx->cb_read)(ctx, (void*)buf, (size_t)size, ctx->cb_payload);
+ return (ret);
+}
+
+static BIO *
+tls_get_new_cb_bio(struct tls *ctx)
+{
+ BIO *bcb = NULL;
+ if (ctx->cb_read == NULL || ctx->cb_write == NULL)
+ tls_set_errorx(ctx, "no callbacks registered");
+ bcb = BIO_new(BIO_s_cb());
+ if (bcb == NULL) {
+ tls_set_errorx(ctx, "failed to create callback i/o");
+ return (NULL);
+ }
+ BIO_set_cb_write(bcb, tls_bio_cb_write);
+ BIO_set_cb_read(bcb, tls_bio_cb_read);
+ BIO_set_cb_payload(bcb, ctx);
+ return (bcb);
+}
+
+int
+tls_set_cbs(struct tls *ctx, tls_read_cb cb_read, tls_write_cb cb_write,
+ void *cb_payload)
+{
+ int rv = -1;
+ BIO *bcb;
+ ctx->cb_read = cb_read;
+ ctx->cb_write = cb_write;
+ ctx->cb_payload = cb_payload;
+ bcb = tls_get_new_cb_bio(ctx);
+ if (bcb == NULL) {
+ tls_set_errorx(ctx, "failed to create callback i/o");
+ goto err;
+ }
+ SSL_set_bio(ctx->ssl_conn, bcb, bcb);
+ rv = 0;
+err:
+ return (rv);
+}
diff --git src/lib/libtls/tls.h src/lib/libtls/tls.h
index 3e75eb7..f236245 100644
--- src/lib/libtls/tls.h
+++ src/lib/libtls/tls.h
@@ -44,6 +44,11 @@ extern "C" {
struct tls;
struct tls_config;
+typedef ssize_t (*tls_read_cb)(void *_ctx, void *_buf, size_t _buflen,
+ void *_payload);
+typedef ssize_t (*tls_write_cb)(void *_ctx, const void *_buf,
+ size_t _buflen, void *_payload);
+
int tls_init(void);
const char *tls_config_error(struct tls_config *_config);
@@ -96,12 +101,16 @@ void tls_free(struct tls *_ctx);
int tls_accept_fds(struct tls *_ctx, struct tls **_cctx, int _fd_read,
int _fd_write);
int tls_accept_socket(struct tls *_ctx, struct tls **_cctx, int _socket);
+int tls_accept_cbs(struct tls *_ctx, struct tls **_cctx,
+ tls_read_cb _cb_read, tls_write_cb _cb_write, void *_cb_payload);
int tls_connect(struct tls *_ctx, const char *_host, const char *_port);
int tls_connect_fds(struct tls *_ctx, int _fd_read, int _fd_write,
const char *_servername);
int tls_connect_servername(struct tls *_ctx, const char *_host,
const char *_port, const char *_servername);
int tls_connect_socket(struct tls *_ctx, int _s, const char *_servername);
+int tls_connect_cbs(struct tls *_ctx, tls_read_cb _cb_read, tls_write_cb
_cb_write,
+ void *_cb_payload, const char *_servername);
int tls_handshake(struct tls *_ctx);
ssize_t tls_read(struct tls *_ctx, void *_buf, size_t _buflen);
ssize_t tls_write(struct tls *_ctx, const void *_buf, size_t _buflen);
diff --git src/lib/libtls/tls_bio_cb.c src/lib/libtls/tls_bio_cb.c
new file mode 100644
index 0000000..f8b24b0
--- /dev/null
+++ src/lib/libtls/tls_bio_cb.c
@@ -0,0 +1,182 @@
+/* $ID$ */
+/*
+ * Copyright (c) 2016 Tobias Pape <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "tls.h"
+#include "tls_internal.h"
+
+#include <openssl/bio.h>
+
+static int cb_write(BIO *b, const char *buf, int num);
+static int cb_read(BIO *b, char *buf, int size);
+static int cb_puts(BIO *b, const char *str);
+static long cb_ctrl(BIO *b, int cmd, long num, void *ptr);
+static int cb_new(BIO *b);
+static int cb_free(BIO *data);
+
+struct bio_cb_st {
+ int (*cb_write)(BIO *h, const char *buf, int num, void *payload);
+ int (*cb_read)(BIO *h, char *buf, int size, void *payload);
+ void *payload;
+};
+
+static BIO_METHOD cb_method = {
+ .type = BIO_TYPE_MEM,
+ .name = "callbacks",
+ .bwrite = cb_write,
+ .bread = cb_read,
+ .bputs = cb_puts,
+ .ctrl = cb_ctrl,
+ .create = cb_new,
+ .destroy = cb_free
+};
+
+
+
+BIO_METHOD *
+BIO_s_cb(void)
+{
+ return (&cb_method);
+}
+
+int
+BIO_set_cb_write(BIO *bi, int (*cb_write)(BIO *h, const char *buf, int num,
void *payload))
+{
+ struct bio_cb_st *b;
+ b = (struct bio_cb_st *)bi->ptr;
+ b->cb_write = cb_write;
+ return (0);
+}
+
+int
+BIO_set_cb_read(BIO *bi, int (*cb_read)(BIO *h, char *buf, int size, void
*payload))
+{
+ struct bio_cb_st *b;
+ b = (struct bio_cb_st *)bi->ptr;
+ b->cb_read = cb_read;
+ return (0);
+}
+
+int
+BIO_set_cb_payload(BIO *bi, void *payload)
+{
+ struct bio_cb_st *b;
+ b = (struct bio_cb_st *)bi->ptr;
+ b->payload = payload;
+ return (0);
+}
+
+void *
+BIO_get_cb_payload(BIO *bi)
+{
+ struct bio_cb_st *b;
+ b = (struct bio_cb_st *)bi->ptr;
+ return (b->payload);
+}
+
+
+
+static int
+cb_new(BIO *bi)
+{
+ struct bio_cb_st *bcb;
+
+ bcb = calloc(1, sizeof(struct bio_cb_st));
+ if (bcb == NULL)
+ return (0);
+ bi->shutdown = 1;
+ bi->init = 1;
+ bi->num = -1;
+ bi->ptr = (char *)bcb;
+ return (1);
+}
+
+static int
+cb_free(BIO *bi)
+{
+ if (bi == NULL)
+ return (0);
+ if (bi->shutdown) {
+ if ((bi->init) && (bi->ptr != NULL)) {
+ struct bio_cb_st *b;
+ b = (struct bio_cb_st *)bi->ptr;
+ free(b);
+ bi->ptr = NULL;
+ }
+ }
+ return (1);
+}
+
+static int
+cb_read(BIO *b, char *buf, int size)
+{
+ int ret = -1;
+ struct bio_cb_st *bcb;
+
+ bcb = (struct bio_cb_st *)b->ptr;
+ ret = (bcb->cb_read)(b, buf, size, bcb->payload);
+ return (ret);
+}
+
+
+
+static int
+cb_write(BIO *b, const char *buf, int num)
+{
+ int ret = -1;
+ struct bio_cb_st *bcb;
+
+ bcb = (struct bio_cb_st *)b->ptr;
+ ret = (bcb->cb_write)(b, buf, num, bcb->payload);
+ return (ret);
+}
+
+static int
+cb_puts(BIO *b, const char *str)
+{
+ int n, ret;
+
+ n = strlen(str);
+ ret = cb_write(b, str, n);
+ return (ret);
+}
+
+static long
+cb_ctrl(BIO *b, int cmd, long num, void *ptr)
+{
+ long ret = 1;
+ switch (cmd) {
+ case BIO_CTRL_GET_CLOSE:
+ ret = (long)b->shutdown;
+ break;
+ case BIO_CTRL_SET_CLOSE:
+ b->shutdown = (int)num;
+ break;
+ case BIO_CTRL_DUP:
+ break;
+ case BIO_CTRL_INFO:
+ case BIO_CTRL_GET:
+ case BIO_CTRL_SET:
+ default:
+ ret = BIO_ctrl(b->next_bio, cmd, num, ptr);
+ }
+ return (ret);
+
+}
diff --git src/lib/libtls/tls_client.c src/lib/libtls/tls_client.c
index 2149552..c5334f8 100644
--- src/lib/libtls/tls_client.c
+++ src/lib/libtls/tls_client.c
@@ -158,6 +158,80 @@ tls_connect_servername(struct tls *ctx, const char *host,
const char *port,
return (rv);
}
+int
+tls_connect_cbs(struct tls *ctx, tls_read_cb cb_read,
+ tls_write_cb cb_write, void *cb_payload, const char *servername)
+{
+ union tls_addr addrbuf;
+ int rv = -1;
+
+ if ((ctx->flags & TLS_CLIENT) == 0) {
+ tls_set_errorx(ctx, "not a client context");
+ goto err;
+ }
+
+ if (servername != NULL) {
+ if ((ctx->servername = strdup(servername)) == NULL) {
+ tls_set_errorx(ctx, "out of memory");
+ goto err;
+ }
+ }
+
+ if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
+ tls_set_errorx(ctx, "ssl context failure");
+ goto err;
+ }
+
+ if (tls_configure_ssl(ctx) != 0)
+ goto err;
+ if (tls_configure_keypair(ctx, ctx->ssl_ctx, ctx->config->keypair, 0)
!= 0)
+ goto err;
+
+ if (ctx->config->verify_name) {
+ if (servername == NULL) {
+ tls_set_errorx(ctx, "server name not specified");
+ goto err;
+ }
+ }
+
+ if (ctx->config->verify_cert &&
+ (tls_configure_ssl_verify(ctx, SSL_VERIFY_PEER) == -1))
+ goto err;
+
+ if ((ctx->ssl_conn = SSL_new(ctx->ssl_ctx)) == NULL) {
+ tls_set_errorx(ctx, "ssl connection failure");
+ goto err;
+ }
+ if (SSL_set_app_data(ctx->ssl_conn, ctx) != 1) {
+ tls_set_errorx(ctx, "ssl application data failure");
+ goto err;
+ }
+
+ if (tls_set_cbs(ctx, cb_read, cb_write, cb_payload) != 0) {
+ tls_set_errorx(ctx, "callback registration failure");
+ goto err;
+ }
+
+ /*
+ * RFC4366 (SNI): Literal IPv4 and IPv6 addresses are not
+ * permitted in "HostName".
+ */
+ if (servername != NULL &&
+ inet_pton(AF_INET, servername, &addrbuf) != 1 &&
+ inet_pton(AF_INET6, servername, &addrbuf) != 1) {
+ if (SSL_set_tlsext_host_name(ctx->ssl_conn, servername) == 0) {
+ tls_set_errorx(ctx, "server name indication failure");
+ goto err;
+ }
+ }
+
+ rv = 0;
+
+ err:
+ return (rv);
+
+}
+
int
tls_connect_socket(struct tls *ctx, int s, const char *servername)
{
diff --git src/lib/libtls/tls_init.3 src/lib/libtls/tls_init.3
index 51b58ae..9fb4545 100644
--- src/lib/libtls/tls_init.3
+++ src/lib/libtls/tls_init.3
@@ -66,8 +66,10 @@
.Nm tls_connect_fds ,
.Nm tls_connect_servername ,
.Nm tls_connect_socket ,
+.Nm tls_connect_cbs ,
.Nm tls_accept_fds ,
.Nm tls_accept_socket ,
+.Nm tls_accept_cbs ,
.Nm tls_handshake ,
.Nm tls_read ,
.Nm tls_write ,
@@ -172,10 +174,14 @@
.Ft "int"
.Fn tls_connect_socket "struct tls *ctx" "int s" "const char *servername"
.Ft "int"
+.Fn tls_connect_cbs "struct tls *ctx" "ssize_t (*tls_read_cb)(void *ctx, void
*buf, size_t buflen, void *payload)" "ssize_t (*tls_write_cb)(void *ctx, const
void *buf, size_t buflen, void *payload)" "void *cb_payload" "const char
*servername"
+.Ft "int"
.Fn tls_accept_fds "struct tls *tls" "struct tls **cctx" "int fd_read" "int
fd_write"
.Ft "int"
.Fn tls_accept_socket "struct tls *tls" "struct tls **cctx" "int socket"
.Ft "int"
+.Fn tls_accept_cbs "struct tls *ctx" "struct tls **cctx" "ssize_t
(*tls_read_cb)(void *ctx, void *buf, size_t buflen, void *payload)" "ssize_t
(*tls_write_cb)(void *ctx, const void *buf, size_t buflen, void *payload)"
"void *cb_payload"
+.Ft "int"
.Fn tls_handshake "struct tls *ctx"
.Ft "ssize_t"
.Fn tls_read "struct tls *ctx" "void *buf" "size_t buflen"
@@ -231,14 +237,18 @@ An already existing socket can be upgraded to a secure
connection by calling
.Fn tls_connect_socket .
Alternatively, a secure connection can be established over a pair of existing
file descriptors by calling
-.Fn tls_connect_fds .
+.Fn tls_connect_fds . Using
+.Fn tls_connect_cbs , read and write callbacks can be specified to handle the
+actual data transfer.
.Pp
A server can accept a new client connection by calling
.Fn tls_accept_socket
on an already established socket connection.
Alternatively, a new client connection can be accepted over a pair of existing
file descriptors by calling
-.Fn tls_accept_fds .
+.Fn tls_accept_fds .Using
+.Fn tls_accept_cbs , read and write callbacks can be specified to handle the
+actual data transfer.
.Pp
The TLS handshake can be completed by calling
.Fn tls_handshake .
diff --git src/lib/libtls/tls_internal.h src/lib/libtls/tls_internal.h
index 01bda4f..41a58fa 100644
--- src/lib/libtls/tls_internal.h
+++ src/lib/libtls/tls_internal.h
@@ -103,6 +103,10 @@ struct tls {
SSL_CTX *ssl_ctx;
X509 *ssl_peer_cert;
struct tls_conninfo *conninfo;
+
+ ssize_t (*cb_read)(void *_ctx, void *_buf, size_t _buflen, void
*_payload);
+ ssize_t (*cb_write)(void *_ctx, const void *_buf, size_t _buflen, void
*_payload);
+ void *cb_payload;
};
struct tls *tls_new(void);
@@ -117,6 +121,11 @@ int tls_configure_ssl_verify(struct tls *ctx, int verify);
int tls_handshake_client(struct tls *ctx);
int tls_handshake_server(struct tls *ctx);
int tls_host_port(const char *hostport, char **host, char **port);
+int tls_set_cbs(struct tls *ctx,
+ ssize_t (*cb_read)(void *_ctx, void *_buf, size_t _buflen, void *_payload),
+ ssize_t (*cb_write)(void *_ctx, const void *_buf, size_t _buflen, void
*_payload),
+ void *cb_payload);
+
int tls_error_set(struct tls_error *error, const char *fmt, ...)
__attribute__((__format__ (printf, 2, 3)))
@@ -145,4 +154,10 @@ void tls_free_conninfo(struct tls_conninfo *conninfo);
int asn1_time_parse(const char *, size_t, struct tm *, int);
+BIO_METHOD * BIO_s_cb(void);
+int BIO_set_cb_write(BIO *bi, int (*cb_write)(BIO *h, const char *buf, int
num, void *payload));
+int BIO_set_cb_read(BIO *bi, int (*cb_read)(BIO *h, char *buf, int size, void
*payload));
+int BIO_set_cb_payload(BIO *bi, void *payload);
+void *BIO_get_cb_payload(BIO *bi);
+
#endif /* HEADER_TLS_INTERNAL_H */
diff --git src/lib/libtls/tls_server.c src/lib/libtls/tls_server.c
index 58dbb60..b05df52 100644
--- src/lib/libtls/tls_server.c
+++ src/lib/libtls/tls_server.c
@@ -110,6 +110,49 @@ tls_configure_server(struct tls *ctx)
return (-1);
}
+int
+tls_accept_cbs(struct tls *ctx, struct tls **cctx, tls_read_cb cb_read,
+ tls_write_cb cb_write, void *cb_payload)
+{
+ struct tls *conn_ctx = NULL;
+
+ if ((ctx->flags & TLS_SERVER) == 0) {
+ tls_set_errorx(ctx, "not a server context");
+ goto err;
+ }
+
+ if ((conn_ctx = tls_server_conn(ctx)) == NULL) {
+ tls_set_errorx(ctx, "connection context failure");
+ goto err;
+ }
+
+ if ((conn_ctx->ssl_conn = SSL_new(ctx->ssl_ctx)) == NULL) {
+ tls_set_errorx(ctx, "ssl failure");
+ goto err;
+ }
+ if (SSL_set_app_data(conn_ctx->ssl_conn, conn_ctx) != 1) {
+ tls_set_errorx(ctx, "ssl application data failure");
+ goto err;
+ }
+
+ if (tls_set_cbs(ctx, cb_read, cb_write, cb_payload) != 0) {
+ tls_set_errorx(ctx, "callback registration failure");
+ goto err;
+ }
+
+ *cctx = conn_ctx;
+
+ return (0);
+
+ err:
+ tls_free(conn_ctx);
+
+ *cctx = NULL;
+
+ return (-1);
+}
+
+
int
tls_accept_socket(struct tls *ctx, struct tls **cctx, int socket)
{