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 <tob...@netshed.de> + * + * 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) {