blob: e769d4028f7e78defcc12834d4f25f23ce7c9df6 [file] [log] [blame] [raw]
/*
Copyright: Boaz Segev, 2018
License: MIT
Feel free to copy, use and enjoy according to the license provided.
*/
#include <fio.h>
/**
* This implementation of the facil.io SSL/TLS wrapper API is the default
* implementation that will be used when no SSL/TLS library is available...
*
* ... without modification, this implementation crashes the program.
*
* The implementation can be USED AS A TEMPLATE for future implementations.
*
* This implementation is optimized for ease of development rather than memory
* consumption.
*/
#include "fio_tls.h"
#if 1 /* TODO: place library compiler flags here */
#define REQUIRE_LIBRARY()
#define FIO_TLS_WEAK
/* TODO: delete me! */
#undef FIO_TLS_WEAK
#define FIO_TLS_WEAK __attribute__((weak))
#if !FIO_IGNORE_TLS_IF_MISSING
#undef REQUIRE_LIBRARY
#define REQUIRE_LIBRARY() \
FIO_LOG_FATAL("No supported SSL/TLS library available."); \
exit(-1);
#endif
/* STOP deleting after this line */
/* *****************************************************************************
The SSL/TLS helper data types (can be left as is)
***************************************************************************** */
#define FIO_INCLUDE_STR 1
#define FIO_FORCE_MALLOC_TMP 1
#include <fio.h>
typedef struct {
fio_str_s name; /* fio_str_s provides cache locality for small strings */
void (*callback)(intptr_t uuid, void *udata);
} alpn_s;
static inline int fio_alpn_cmp(const alpn_s *dest, const alpn_s *src) {
return fio_str_iseq(&dest->name, &src->name);
}
static inline void fio_alpn_copy(alpn_s *dest, alpn_s *src) {
*dest = (alpn_s){.name = FIO_STR_INIT, .callback = src->callback};
fio_str_concat(&dest->name, &src->name);
}
static inline void fio_alpn_destroy(alpn_s *obj) { fio_str_free(&obj->name); }
#define FIO_ARY_NAME alpn_ary
#define FIO_ARY_TYPE alpn_s
#define FIO_ARY_COMPARE(k1, k2) fio_alpn_cmp(&(k1), &(k2))
#define FIO_ARY_COPY(dest, obj) fio_alpn_copy(&(dest), &(obj))
#define FIO_ARY_DESTROY(key) fio_alpn_destroy(&(key))
#define FIO_FORCE_MALLOC_TMP 1
#include <fio.h>
typedef struct {
fio_str_s private_key;
fio_str_s public_key;
fio_str_s password;
} cert_s;
static inline int fio_tls_cert_cmp(const cert_s *dest, const cert_s *src) {
return fio_str_iseq(&dest->private_key, &src->private_key);
}
static inline void fio_tls_cert_copy(cert_s *dest, cert_s *src) {
*dest = (cert_s){
.private_key = FIO_STR_INIT,
.public_key = FIO_STR_INIT,
.password = FIO_STR_INIT,
};
fio_str_concat(&dest->private_key, &src->private_key);
fio_str_concat(&dest->public_key, &src->public_key);
fio_str_concat(&dest->password, &src->password);
}
static inline void fio_tls_cert_destroy(cert_s *obj) {
fio_str_free(&obj->private_key);
fio_str_free(&obj->public_key);
fio_str_free(&obj->password);
}
#define FIO_ARY_NAME cert_ary
#define FIO_ARY_TYPE cert_s
#define FIO_ARY_COMPARE(k1, k2) (fio_tls_cert_cmp(&(k1), &(k2)))
#define FIO_ARY_COPY(dest, obj) fio_tls_cert_copy(&(dest), &(obj))
#define FIO_ARY_DESTROY(key) fio_tls_cert_destroy(&(key))
#define FIO_FORCE_MALLOC_TMP 1
#include <fio.h>
typedef struct {
fio_str_s pem;
} trust_s;
static inline int fio_tls_trust_cmp(const trust_s *dest, const trust_s *src) {
return fio_str_iseq(&dest->pem, &src->pem);
}
static inline void fio_tls_trust_copy(trust_s *dest, trust_s *src) {
*dest = (trust_s){
.pem = FIO_STR_INIT,
};
fio_str_concat(&dest->pem, &src->pem);
}
static inline void fio_tls_trust_destroy(trust_s *obj) {
fio_str_free(&obj->pem);
}
#define FIO_ARY_NAME trust_ary
#define FIO_ARY_TYPE trust_s
#define FIO_ARY_COMPARE(k1, k2) (fio_tls_trust_cmp(&(k1), &(k2)))
#define FIO_ARY_COPY(dest, obj) fio_tls_trust_copy(&(dest), &(obj))
#define FIO_ARY_DESTROY(key) fio_tls_trust_destroy(&(key))
#define FIO_FORCE_MALLOC_TMP 1
#include <fio.h>
/* *****************************************************************************
The SSL/TLS type
***************************************************************************** */
/** An opaque type used for the SSL/TLS functions. */
struct fio_tls_s {
alpn_ary_s alpn; /* ALPN is the name for the protocol selection extension */
cert_ary_s sni; /* SNI is the name for the server name extension */
trust_ary_s trust; /* SNI is the name for the server name extension */
/************ TODO: implementation data fields go here ******************/
};
/* *****************************************************************************
SSL/TLS Context (re)-building - TODO: add implementation details
***************************************************************************** */
/** Called when the library specific data for the context should be destroyed */
static void fio_tls_destroy_context(fio_tls_s *tls) {
/* TODO: Library specific implementation */
FIO_LOG_DEBUG("destroyed TLS context %p", (void *)tls);
}
/** Called when the library specific data for the context should be built */
static void fio_tls_build_context(fio_tls_s *tls) {
fio_tls_destroy_context(tls);
/* TODO: Library specific implementation */
/* Certificates */
FIO_ARY_FOR(&tls->sni, pos) {
fio_str_info_s k = fio_str_info(&pos->private_key);
fio_str_info_s p = fio_str_info(&pos->public_key);
fio_str_info_s pw = fio_str_info(&pos->password);
if (p.len && k.len) {
/* TODO: attache certificate */
(void)pw;
} else {
/* TODO: self signed certificate */
}
}
/* ALPN Protocols */
FIO_ARY_FOR(&tls->alpn, pos) {
fio_str_info_s name = fio_str_info(&pos->name);
(void)name;
// map to pos->callback;
}
/* Peer Verification / Trust */
if (trust_ary_count(&tls->trust)) {
/* TODO: enable peer verification */
/* TODO: Add each ceriticate in the PEM to the trust "store" */
FIO_ARY_FOR(&tls->trust, pos) {
fio_str_info_s pem = fio_str_info(&pos->pem);
(void)pem;
}
}
FIO_LOG_DEBUG("(re)built TLS context %p", (void *)tls);
}
/* *****************************************************************************
SSL/TLS RW Hooks - TODO: add implementation details
***************************************************************************** */
/* TODO: this is an example implementation - fix for specific library. */
#define TLS_BUFFER_LENGTH (1 << 15)
typedef struct {
size_t len;
char buffer[TLS_BUFFER_LENGTH];
} buffer_s;
/**
* Implement reading from a file descriptor. Should behave like the file
* system `read` call, including the setup or errno to EAGAIN / EWOULDBLOCK.
*
* Note: facil.io library functions MUST NEVER be called by any r/w hook, or a
* deadlock might occur.
*/
static ssize_t fio_tls_read(intptr_t uuid, void *udata, void *buf,
size_t count) {
ssize_t ret = read(fio_uuid2fd(uuid), buf, count);
if (ret > 0) {
FIO_LOG_DEBUG("Read %zd bytes from %p", ret, (void *)uuid);
}
return ret;
(void)udata;
}
/**
* When implemented, this function will be called to flush any data remaining
* in the internal buffer.
*
* The function should return the number of bytes remaining in the internal
* buffer (0 is a valid response) or -1 (on error).
*
* Note: facil.io library functions MUST NEVER be called by any r/w hook, or a
* deadlock might occur.
*/
static ssize_t fio_tls_flush(intptr_t uuid, void *udata) {
buffer_s *buffer = udata;
if (!buffer->len) {
FIO_LOG_DEBUG("Flush empty for %p", (void *)uuid);
return 0;
}
ssize_t r = write(fio_uuid2fd(uuid), buffer->buffer, buffer->len);
if (r < 0)
return -1;
if (r == 0) {
errno = ECONNRESET;
return -1;
}
size_t len = buffer->len - r;
if (len)
memmove(buffer->buffer, buffer->buffer + r, len);
buffer->len = len;
FIO_LOG_DEBUG("Sent %zd bytes to %p", r, (void *)uuid);
return r;
}
/**
* Implement writing to a file descriptor. Should behave like the file system
* `write` call.
*
* If an internal buffer is implemented and it is full, errno should be set to
* EWOULDBLOCK and the function should return -1.
*
* Note: facil.io library functions MUST NEVER be called by any r/w hook, or a
* deadlock might occur.
*/
static ssize_t fio_tls_write(intptr_t uuid, void *udata, const void *buf,
size_t count) {
buffer_s *buffer = udata;
size_t can_copy = TLS_BUFFER_LENGTH - buffer->len;
if (can_copy > count)
can_copy = count;
if (!can_copy)
goto would_block;
memcpy(buffer->buffer + buffer->len, buf, can_copy);
buffer->len += can_copy;
FIO_LOG_DEBUG("Copied %zu bytes to %p", can_copy, (void *)uuid);
fio_tls_flush(uuid, udata);
return can_copy;
would_block:
errno = EWOULDBLOCK;
return -1;
}
/**
* The `close` callback should close the underlying socket / file descriptor.
*
* If the function returns a non-zero value, it will be called again after an
* attempt to flush the socket and any pending outgoing buffer.
*
* Note: facil.io library functions MUST NEVER be called by any r/w hook, or a
* deadlock might occur.
* */
static ssize_t fio_tls_before_close(intptr_t uuid, void *udata) {
FIO_LOG_DEBUG("The `before_close` callback was called for %p", (void *)uuid);
return 1;
(void)udata;
}
/**
* Called to perform cleanup after the socket was closed.
* */
static void fio_tls_cleanup(void *udata) { fio_free(udata); }
static fio_rw_hook_s FIO_TLS_HOOKS = {
.read = fio_tls_read,
.write = fio_tls_write,
.before_close = fio_tls_before_close,
.flush = fio_tls_flush,
.cleanup = fio_tls_cleanup,
};
static size_t fio_tls_handshake(intptr_t uuid, void *udata) {
/*TODO: test for handshake completion */
if (0 /*handshake didn't complete */)
return 0;
if (fio_rw_hook_replace_unsafe(uuid, &FIO_TLS_HOOKS, udata) == 0) {
FIO_LOG_DEBUG("Completed TLS handshake for %p", (void *)uuid);
} else {
FIO_LOG_DEBUG("Something went wrong during TLS handshake for %p",
(void *)uuid);
}
return 1;
}
static ssize_t fio_tls_read4handshake(intptr_t uuid, void *udata, void *buf,
size_t count) {
FIO_LOG_DEBUG("TLS handshake from read %p", (void *)uuid);
if (fio_tls_handshake(uuid, udata))
return fio_tls_read(uuid, udata, buf, count);
errno = EWOULDBLOCK;
return -1;
}
static ssize_t fio_tls_write4handshake(intptr_t uuid, void *udata,
const void *buf, size_t count) {
FIO_LOG_DEBUG("TLS handshake from write %p", (void *)uuid);
if (fio_tls_handshake(uuid, udata))
return fio_tls_write(uuid, udata, buf, count);
errno = EWOULDBLOCK;
return -1;
}
static ssize_t fio_tls_flush4handshake(intptr_t uuid, void *udata) {
FIO_LOG_DEBUG("TLS handshake from flush %p", (void *)uuid);
if (fio_tls_handshake(uuid, udata))
return fio_tls_flush(uuid, udata);
/* TODO: return a positive value only if handshake requires a write */
return 1;
}
static fio_rw_hook_s FIO_TLS_HANDSHAKE_HOOKS = {
.read = fio_tls_read4handshake,
.write = fio_tls_write4handshake,
.before_close = fio_tls_before_close,
.flush = fio_tls_flush4handshake,
.cleanup = fio_tls_cleanup,
};
static inline void fio_tls_attach2uuid(intptr_t uuid, fio_tls_s *tls,
void *udata, uint8_t is_server) {
/* TODO: this is only an example implementation - fix for specific library */
if (is_server) {
/* Server mode (accept) */
FIO_LOG_DEBUG("Attaching TLS read/write hook for %p (server mode).",
(void *)uuid);
} else {
/* Client mode (connect) */
FIO_LOG_DEBUG("Attaching TLS read/write hook for %p (client mode).",
(void *)uuid);
}
/* common implementation (TODO) */
buffer_s *connection_data = fio_malloc(sizeof(*connection_data));
FIO_ASSERT_ALLOC(connection_data);
fio_rw_hook_set(uuid, &FIO_TLS_HANDSHAKE_HOOKS,
connection_data); /* 32Kb buffer */
if (alpn_ary_count(&tls->alpn))
alpn_ary_get(&tls->alpn, 0).callback(uuid, udata);
}
/* *****************************************************************************
SSL/TLS API implementation - this can be pretty much used as is...
***************************************************************************** */
/**
* Creates a new SSL/TLS context / settings object with a default certificate
* (if any).
*/
fio_tls_s *FIO_TLS_WEAK fio_tls_new(const char *server_name, const char *key,
const char *cert, const char *pk_password) {
REQUIRE_LIBRARY();
fio_tls_s *tls = calloc(sizeof(*tls), 1);
fio_tls_cert_add(tls, server_name, key, cert, pk_password);
return tls;
}
/**
* Adds a certificate a new SSL/TLS context / settings object.
*
* fio_tls_cert_add(tls, FIO_TLS_CERT("www.example.com",
* "private_key.key",
* "public_key.crt" ));
*/
void FIO_TLS_WEAK fio_tls_cert_add(fio_tls_s *tls, const char *server_name,
const char *key, const char *cert,
const char *pk_password) {
REQUIRE_LIBRARY();
cert_s c = {
.private_key = FIO_STR_INIT,
.public_key = FIO_STR_INIT,
.password = FIO_STR_INIT_STATIC2(pk_password,
(pk_password ? strlen(pk_password) : 0)),
};
if (key && cert) {
if (fio_str_readfile(&c.private_key, key, 0, 0).data == NULL)
goto file_missing;
if (fio_str_readfile(&c.public_key, cert, 0, 0).data == NULL)
goto file_missing;
cert_ary_push(&tls->sni, c);
} else if (server_name) {
/* Self-Signed TLS Certificates */
c.private_key = FIO_STR_INIT_STATIC(server_name);
cert_ary_push(&tls->sni, c);
}
fio_tls_cert_destroy(&c);
fio_tls_build_context(tls);
return;
file_missing:
FIO_LOG_FATAL("TLS certificate file missing for either %s or %s or both.",
key, cert);
exit(-1);
}
/**
* Adds an ALPN protocol callback to the SSL/TLS context.
*
* The first protocol added will act as the default protocol to be selected.
*/
void FIO_TLS_WEAK fio_tls_proto_add(fio_tls_s *tls, const char *protocol_name,
void (*callback)(intptr_t uuid,
void *udata)) {
REQUIRE_LIBRARY();
alpn_s tmp = {
.name = FIO_STR_INIT_STATIC(protocol_name),
.callback = callback,
};
if (fio_str_len(&tmp.name) > 255) {
FIO_LOG_ERROR(
"fio_tls_proto_add called with a protocol name exceeding 255 bytes.");
return;
}
alpn_ary_push(&tls->alpn, tmp);
fio_alpn_destroy(&tmp);
fio_tls_build_context(tls);
}
/**
* Adds a certificate to the "trust" list, which automatically adds a peer
* verification requirement.
*
* fio_tls_trust(tls, "google-ca.pem" );
*/
void FIO_TLS_WEAK fio_tls_trust(fio_tls_s *tls, const char *public_cert_file) {
REQUIRE_LIBRARY();
trust_s c = {
.pem = FIO_STR_INIT,
};
if (!public_cert_file)
return;
if (fio_str_readfile(&c.pem, public_cert_file, 0, 0).data == NULL)
goto file_missing;
trust_ary_push(&tls->trust, c);
fio_tls_trust_destroy(&c);
fio_tls_build_context(tls);
return;
file_missing:
FIO_LOG_FATAL("TLS certificate file missing for %s ", public_cert_file);
exit(-1);
}
/**
* Establishes an SSL/TLS connection as an SSL/TLS Server, using the specified
* context / settings object.
*
* The `uuid` should be a socket UUID that is already connected to a peer (i.e.,
* the result of `fio_accept`).
*
* The `udata` is an opaque user data pointer that is passed along to the
* protocol selected (if any protocols were added using `fio_tls_proto_add`).
*/
void FIO_TLS_WEAK fio_tls_accept(intptr_t uuid, fio_tls_s *tls, void *udata) {
REQUIRE_LIBRARY();
fio_tls_attach2uuid(uuid, tls, udata, 1);
}
/**
* Establishes an SSL/TLS connection as an SSL/TLS Client, using the specified
* context / settings object.
*
* The `uuid` should be a socket UUID that is already connected to a peer (i.e.,
* one received by a `fio_connect` specified callback `on_connect`).
*
* The `udata` is an opaque user data pointer that is passed along to the
* protocol selected (if any protocols were added using `fio_tls_proto_add`).
*/
void FIO_TLS_WEAK fio_tls_connect(intptr_t uuid, fio_tls_s *tls, void *udata) {
REQUIRE_LIBRARY();
fio_tls_attach2uuid(uuid, tls, udata, 0);
}
/**
* Destroys the SSL/TLS context / settings object and frees any related
* resources / memory.
*/
void FIO_TLS_WEAK fio_tls_destroy(fio_tls_s *tls) {
if (!tls)
return;
REQUIRE_LIBRARY();
fio_tls_destroy_context(tls);
alpn_ary_free(&tls->alpn);
cert_ary_free(&tls->sni);
free(tls);
}
#endif /* Library compiler flags */