bqqbarbhg/bq_websocket

What do you think of BearSSL?

Closed this issue · 4 comments

frink commented

I was wondering if BearSSL might be a better license fit than OpenSSL...

I initially chose OpenSSL as it's the "default" and many users are likely to have it installed. That said by default there is no TLS support in the platform layer (except on Apple platforms where the OS provides TLS), so the OpenSSL license should be currently opt-in.

All TLS code is wrapped in an internal tls_ API so adding new TLS backends should be straightforward. I don't want to bloat the platform header with every possible TLS implementation but adding a couple more wouldn't hurt!

BearSSL itself seems a bit niche and is described to be "beta-quality" so I'm a bit concerned about how secure it is, but the choice would be up to the user. There's also Mbed TLS (Apache 2.0, backed by ARM) that might offer some of the same benefits while being a bit more widely used?

Here's the current TLS implementation bits in the platform code for perspective:

OpenSSL

#if BQWS_PT_USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#define BQWS_PT_HAS_OPENSSL
typedef struct {
bool connected;
SSL *ssl;
} pt_tls;
typedef struct {
SSL_CTX *ctx;
} pt_tls_server;
typedef struct {
SSL_CTX *client_ctx;
} pt_tls_global;
static pt_tls_global g_tls;
static void pt_fail_ssl(const char *func)
{
t_err.function = func;
t_err.type = BQWS_PT_ERRTYPE_OPENSSL;
t_err.data = ERR_get_error();
}
static bool tls_init(const bqws_pt_init_opts *opts)
{
int res;
SSL_library_init();
g_tls.client_ctx = SSL_CTX_new(SSLv23_client_method());
if (!g_tls.client_ctx) { pt_fail_ssl("SSL_CTX_new()"); return false; }
if (opts->ca_filename) {
res = SSL_CTX_load_verify_locations(g_tls.client_ctx, opts->ca_filename, NULL);
if (!res) { pt_fail_ssl("SSL_CTX_load_verify_locations()"); return false; }
}
long flags = SSL_OP_NO_COMPRESSION;
SSL_CTX_set_options(g_tls.client_ctx, flags);
long mode = SSL_MODE_ENABLE_PARTIAL_WRITE;
SSL_CTX_set_mode(g_tls.client_ctx, mode);
return true;
}
static void tls_shutdown()
{
SSL_CTX_free(g_tls.client_ctx);
}
static bool tls_init_client(pt_tls *tls, os_socket s, const bqws_pt_connect_opts *pt_opts, const bqws_opts *opts, const bqws_client_opts *client_opts)
{
tls->ssl = SSL_new(g_tls.client_ctx);
if (!tls->ssl) return false;
BIO *bio = BIO_new_socket((int)s, 0);
if (!bio) return false;
// SSL_free() will free the BIO internally
SSL_set_bio(tls->ssl, bio, bio);
if (!pt_opts->insecure_no_verify_host) {
const char *host = client_opts->host;
if (!host || !*host) return false;
X509_VERIFY_PARAM *param = SSL_get0_param(tls->ssl);
X509_VERIFY_PARAM_set_hostflags(param, /* X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS */ 0x4);
X509_VERIFY_PARAM_set1_host(param, host, 0);
SSL_set_verify(tls->ssl, SSL_VERIFY_PEER, NULL);
}
return true;
}
static bool tls_init_server(pt_tls_server *tls, const bqws_pt_listen_opts *pt_opts)
{
tls->ctx = SSL_CTX_new(SSLv23_server_method());
if (!tls->ctx) { pt_fail_ssl("SSL_CTX_new()"); return false; }
int res;
if (pt_opts->certificate_file) {
res = SSL_CTX_use_certificate_file(tls->ctx, pt_opts->certificate_file, SSL_FILETYPE_PEM);
if (!res) { pt_fail_ssl("SSL_CTX_use_certificate_file()"); return false; }
}
if (pt_opts->private_key_file) {
res = SSL_CTX_use_PrivateKey_file(tls->ctx, pt_opts->private_key_file, SSL_FILETYPE_PEM);
if (!res) { pt_fail_ssl("SSL_CTX_use_PrivateKey_file()"); return false; }
}
long flags = SSL_OP_NO_COMPRESSION;
SSL_CTX_set_options(tls->ctx, flags);
long mode = SSL_MODE_ENABLE_PARTIAL_WRITE;
SSL_CTX_set_mode(tls->ctx, mode);
return true;
}
static void tls_free_server(pt_tls_server *tls)
{
if (tls->ctx) {
SSL_CTX_free(tls->ctx);
}
}
static bool tls_init_accept(pt_tls *tls, pt_tls_server *tls_server, os_socket s)
{
tls->ssl = SSL_new(tls_server->ctx);
if (!tls->ssl) return false;
BIO *bio = BIO_new_socket((int)s, 0);
if (!bio) return false;
// SSL_free() will free the BIO internally
SSL_set_bio(tls->ssl, bio, bio);
return true;
}
static void tls_free(pt_tls *tls)
{
if (tls->ssl) SSL_free(tls->ssl);
}
static bool tls_imp_connect(pt_tls *tls)
{
int res = SSL_connect(tls->ssl);
if (res <= 0) {
int err = SSL_get_error(tls->ssl, res);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
// Did not fail, just did not connect yet
return true;
} else {
pt_fail_ssl("SSL_connect()");
return false;
}
}
tls->connected = true;
return true;
}
static size_t tls_send(pt_tls *tls, const void *data, size_t size)
{
if (!tls->connected) {
if (!tls_imp_connect(tls)) return SIZE_MAX;
if (!tls->connected) return 0;
}
if (size > INT_MAX) size = INT_MAX;
int res = SSL_write(tls->ssl, data, (int)size);
if (res <= 0) {
int err = SSL_get_error(tls->ssl, res);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
return 0;
} else {
pt_fail_ssl("SSL_write()");
return SIZE_MAX;
}
}
return (size_t)res;
}
static size_t tls_recv(pt_tls *tls, void *data, size_t size)
{
if (!tls->connected) {
if (!tls_imp_connect(tls)) return SIZE_MAX;
if (!tls->connected) return 0;
}
if (size > INT_MAX) size = INT_MAX;
int res = SSL_read(tls->ssl, data, (int)size);
if (res <= 0) {
int err = SSL_get_error(tls->ssl, res);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
return 0;
} else {
pt_fail_ssl("SSL_read()");
return SIZE_MAX;
}
}
return (size_t)res;
}
#else

No SSL support

#else
typedef struct {
int unused;
} pt_tls;
typedef struct {
int unused;
} pt_tls_server;
static bool tls_init(const bqws_pt_init_opts *opts)
{
return true;
}
static void tls_shutdown()
{
}
static bool tls_init_client(pt_tls *tls, os_socket s, const bqws_pt_connect_opts *pt_opts, const bqws_opts *opts, const bqws_client_opts *client_opts)
{
pt_fail_pt("tls_init_client()", BQWS_PT_ERR_NO_TLS);
return false;
}
static bool tls_init_server(pt_tls_server *tls, const bqws_pt_listen_opts *pt_opts)
{
pt_fail_pt("tls_init_client()", BQWS_PT_ERR_NO_TLS);
return false;
}
static void tls_free_server(pt_tls_server *tls)
{
}
static bool tls_init_accept(pt_tls *tls, pt_tls_server *tls_server, os_socket s)
{
bqws_assert(0 && "Should never get here");
return false;
}
static void tls_free(pt_tls *tls)
{
}
static size_t tls_send(pt_tls *tls, const void *data, size_t size)
{
bqws_assert(0 && "Should never get here");
return SIZE_MAX;
}
static size_t tls_recv(pt_tls *tls, void *data, size_t size)
{
bqws_assert(0 && "Should never get here");
return SIZE_MAX;
}
#endif

frink commented

I think the reason BearSSL is still beta (has been for the past two years...) is because they don't have a a confirmation of large scale battle testing yet. Several small projects are using BearSSL now without known issue. I only suggest BearSSL to keep the resulting binary MIT licensed. MbedTLS is a great choice too...

I'm looking at building a cross platform app using Sokol and was looking at your library as a possible websocket client. (You don't provide a server side do you? Do you suggest one?) I really want something lean but I am creating something that will be both a client and server doing peer connectivity. I'm thinking about separating the server and client apps so I may use separate libs anyway. Any thoughts/suggestions?

Yea adding some alternative SSL implementation would be good at some point for sure! This library does actually offer both client and server WebSockets! Well even though the README.md currently only mentions the client usage, need to fix that :D

Here's a simple server example: https://github.com/bqqbarbhg/bq_websocket/blob/master/examples/echo_server_pt.c?ts=4

Also a game example using Sokol: https://github.com/bqqbarbhg/bq_websocket/blob/master/examples/game/game.c?ts=4

frink commented

Please also fix the main description to include this morsel. I like your implementation more than many. I think it will work perfect for my needs. Very excited indeed!