WIP: Adds MbedTLS support to Cytoplasm #54
14
CHANGELOG.md
|
@ -5,6 +5,20 @@ Cytoplasm. It is intended to be updated with every commit that makes a user-faci
|
|||
change worth reporting in the change log. As such, it changes frequently between
|
||||
releases. Final change log entries are published as [Releases](releases).
|
||||
|
||||
## v0.5.0
|
||||
|
||||
**TO BE WRITTEN**
|
||||
|
||||
### Breaking Changes
|
||||
- `ShaToHex` now requires what hash was used.
|
||||
|
||||
### New Features
|
||||
- LMDB support is available with the `--with-lmdb` flag.
|
||||
- MbedTLS summort is available with the `--with-mbed` flag. Please note that Cytoplasm
|
||||
programs using it must be used with the `CYTO_TLS_CA` environment defined to map to a
|
||||
valid CA file, and that the `CYTO_TLS_SEED` flag can be set to use a seedfile, increasing
|
||||
entropy on systems where it is unavailable.
|
||||
|
||||
## v0.4.1
|
||||
|
||||
Cytoplasm is now a C99 library! Upgrading from C89 to C99 makes Cytoplasm more portable
|
||||
|
|
4
configure
vendored
|
@ -76,6 +76,10 @@ for arg in $SCRIPT_ARGS; do
|
|||
TLS_IMPL="TLS_LIBRESSL"
|
||||
TLS_LIBS="-ltls -lcrypto -lssl"
|
||||
;;
|
||||
--with-mbed)
|
||||
TLS_IMPL="TLS_MBEDTLS"
|
||||
TLS_LIBS="-lmbedtls"
|
||||
;;
|
||||
--disable-tls)
|
||||
TLS_IMPL=""
|
||||
TLS_LIBS=""
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
|
||||
#define TLS_LIBRESSL 2
|
||||
#define TLS_OPENSSL 3
|
||||
#define TLS_MBEDTLS 4
|
||||
|
||||
/**
|
||||
* Create a new TLS client stream using the given file descriptor and
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <limits.h>
|
||||
|
||||
/* TODO: Verify LibreSSL support later */
|
||||
#include <Tls.h>
|
||||
#if defined(TLS_IMPL) && (TLS_IMPL == TLS_OPENSSL)
|
||||
|
||||
#include <openssl/sha.h>
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
|
||||
/* TODO: Verify LibreSSL support later */
|
||||
#include <Tls.h>
|
||||
#if defined(TLS_IMPL) && (TLS_IMPL == TLS_OPENSSL)
|
||||
|
||||
#include <openssl/sha.h>
|
||||
|
|
369
src/Tls/TlsMbedTLS.c
Normal file
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Tls.h>
|
||||
|
||||
#if TLS_IMPL == TLS_MBEDTLS
|
||||
|
||||
#include "mbedtls/net_sockets.h"
|
||||
#include "mbedtls/ssl.h"
|
||||
#include "mbedtls/entropy.h"
|
||||
#include "mbedtls/ctr_drbg.h"
|
||||
#include "mbedtls/debug.h"
|
||||
#include "mbedtls/error.h"
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Log.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* #include statements and any implementation structures
|
||||
* needed should go here.
|
||||
*/
|
||||
typedef struct MbedCookie {
|
||||
int fd;
|
||||
bool serverside;
|
||||
|
||||
mbedtls_net_context serverFD;
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context ctrDrbg;
|
||||
mbedtls_ssl_context ssl;
|
||||
mbedtls_ssl_config conf;
|
||||
mbedtls_x509_crt cert;
|
||||
|
||||
mbedtls_pk_context serverkey;
|
||||
} MbedCookie;
|
||||
|
||||
static void my_debug(void *ctx, int level,
|
||||
const char *file, int line,
|
||||
const char *str)
|
||||
{
|
||||
((void) level);
|
||||
|
||||
fprintf((FILE *) ctx, "%s:%04d: %s\n", file, line, str);
|
||||
fflush((FILE *) ctx);
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
TlsInitClient(int fd, const char *serverName)
|
||||
{
|
||||
MbedCookie *cookie;
|
||||
char *cafile;
|
||||
char *seed;
|
||||
int err;
|
||||
if (!serverName)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cookie = Malloc(sizeof(MbedCookie));
|
||||
memset(cookie, 0, sizeof(MbedCookie));
|
||||
cookie->fd = fd;
|
||||
cookie->serverside = false;
|
||||
|
||||
/* Initialise MbedTLS */
|
||||
mbedtls_net_init(&cookie->serverFD);
|
||||
mbedtls_ssl_init(&cookie->ssl);
|
||||
mbedtls_ssl_config_init(&cookie->conf);
|
||||
mbedtls_x509_crt_init(&cookie->cert);
|
||||
mbedtls_ctr_drbg_init(&cookie->ctrDrbg);
|
||||
mbedtls_pk_init(&cookie->serverkey);
|
||||
|
||||
mbedtls_entropy_init(&cookie->entropy);
|
||||
err = mbedtls_ctr_drbg_seed(
|
||||
&cookie->ctrDrbg,
|
||||
mbedtls_entropy_func,
|
||||
&cookie->entropy,
|
||||
|
||||
(const unsigned char *) serverName, strlen(serverName)
|
||||
);
|
||||
if (err != 0)
|
||||
{
|
||||
Log(LOG_ERR, "MbedTLS failure on client init: %d", err);
|
||||
goto error;
|
||||
}
|
||||
/* Add a source of entropy if possible(using the CYTO_TLS_SEED env).
|
||||
* Note that we ignore the error code. */
|
||||
seed = getenv("CYTO_TLS_SEED");
|
||||
mbedtls_entropy_update_seed_file(&cookie->entropy, seed);
|
||||
|
||||
/* TODO */
|
||||
cookie->serverFD.fd = fd;
|
||||
|
||||
err = mbedtls_ssl_config_defaults(
|
||||
&cookie->conf,
|
||||
MBEDTLS_SSL_IS_CLIENT,
|
||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT
|
||||
);
|
||||
jordan
commented
Again, why should this come from the environment? Is there a way we can somehow just use the system's CA file? Again, why should this come from the environment? Is there a way we can somehow just use the system's CA file?
lda
commented
Just made it check for known certificate paths(though I've kept the environment, in case any users may still want to use a custom directory/are really unlucky in their OS setups) Just made it check for known certificate paths(though I've kept the environment, in case any users may still want to use a custom directory/are really unlucky in their OS setups)
jordan
commented
Okay, I think I'm good with what you've done here. I do have a question though: Can you load multiple PEMs? Like, why bother with However, I don't think this behavior should be specific to Mbed. If we're going to use For example, say I'm a sysadmin at a large corporation with tons of self-signed certificates for internal services. Instead of installing the certificate authority to the system, I want to just set Okay, I think I'm good with what you've done here. I do have a question though: Can you load *multiple* PEMs? Like, why bother with `return true` on success, when you could just keep going and load all the CA stores you can find? If the answer is no, you can only load one CA store, then that makes sense. I'm just curious if we could be *even more* flexible and load multiple.
**However**, I don't think this behavior should be specific to Mbed. If we're going to use `CYTO_TLS_CA` to modify Cytoplasm's runtime behavior in loading certificate stores, shouldn't all of the *other* TLS implementations honor this environment variable too?
For example, say I'm a sysadmin at a large corporation with tons of self-signed certificates for internal services. Instead of installing the certificate authority to the system, I want to just set `CYTO_TLS_CA` to my CA store. I'm on a Linux system so I can use OpenSSL, but the TLS implementation really shouldn't matter. Though I guess it might determine what format the CA store must be in. But the point is, the behavior should be consistent across all implementations.
lda
commented
You actually can(if you look under the MbedTLS hood, loading a directory of PEM files is essentially just loading every PEM directly), so I just added that. As for the > Can you load *multiple* PEMs?
You actually *can*(if you look under the MbedTLS hood, loading a directory of PEM files is essentially just loading every PEM directly), so I just added that.
As for the `CYTO_TLS_CA`, I'm not sure I can exactly replicate that behavior around LibreSSL, as the functions used to replace certs with it seem to *overwrite* the current option, rather than just adding it alongside. We *could* just also do that on the MbedTLS side, but I don't know if that'd be a good thing...
jordan
commented
Ah, that's a bummer. I suppose maybe it would suffice to have Doe that make sense? Does that sound like a good idea? Ah, that's a bummer. I suppose maybe it would suffice to have `CYTO_TLS_CA` replace the system's CA store for all implementations. In other words, have Libre and Open check `CYTO_TLS_CA` and load the store given by that variable if it is set, otherwise do nothing and assume that they load the system store. For Mbed, only check and load the other certificate stores if `CYTO_TLS_CA` is unset, so that the behavior is consistent across all three implementations: if `CYTO_TLS_CA` is set, use *only* that store, otherwise use the system stores, and in the case of Mbed, try to determine what those stores are.
Doe that make sense? Does that sound like a good idea?
|
||||
if (err != 0)
|
||||
{
|
||||
char message[256];
|
||||
mbedtls_strerror(err, message, 255);
|
||||
Log(LOG_ERR, "MbedTLS failure on client certs: %s", message);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Setup key verification */
|
||||
cafile = getenv("CYTO_TLS_CA");
|
||||
if ((err = mbedtls_x509_crt_parse_file(&cookie->cert, cafile)) != 0)
|
||||
{
|
||||
char message[256];
|
||||
mbedtls_strerror(err, message, 255);
|
||||
Log(LOG_ERR, "MbedTLS failure on client certs: %s", message);
|
||||
//goto error;
|
||||
}
|
||||
mbedtls_ssl_conf_ca_chain(&cookie->conf, &cookie->cert, NULL);
|
||||
|
||||
/* Setup some callbacks */
|
||||
mbedtls_ssl_conf_rng(
|
||||
&cookie->conf,
|
||||
mbedtls_ctr_drbg_random,
|
||||
&cookie->ctrDrbg
|
||||
);
|
||||
mbedtls_ssl_conf_dbg(&cookie->conf, my_debug, stdout);
|
||||
if ((err = mbedtls_ssl_setup(&cookie->ssl, &cookie->conf)) != 0)
|
||||
{
|
||||
char message[256];
|
||||
mbedtls_strerror(err, message, 255);
|
||||
Log(LOG_ERR, "MbedTLS failure on SSL setup: %s", message);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Setup the servername */
|
||||
if ((err = mbedtls_ssl_set_hostname(&cookie->ssl, serverName)) != 0)
|
||||
{
|
||||
char message[256];
|
||||
mbedtls_strerror(err, message, 255);
|
||||
Log(LOG_ERR, "MbedTLS failure on client hostname: %s", message);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Setup some functions */
|
||||
mbedtls_ssl_set_bio(
|
||||
&cookie->ssl, &cookie->serverFD,
|
||||
mbedtls_net_send, mbedtls_net_recv, NULL
|
||||
);
|
||||
return cookie;
|
||||
error:
|
||||
|
||||
mbedtls_net_free(&cookie->serverFD);
|
||||
mbedtls_ssl_free(&cookie->ssl);
|
||||
mbedtls_ssl_config_free(&cookie->conf);
|
||||
mbedtls_ctr_drbg_free(&cookie->ctrDrbg);
|
||||
mbedtls_entropy_free(&cookie->entropy);
|
||||
Free(cookie);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *
|
||||
TlsInitServer(int fd, const char *crt, const char *key)
|
||||
{
|
||||
MbedCookie *cookie;
|
||||
char *seed;
|
||||
int err;
|
||||
if (!crt || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cookie = Malloc(sizeof(MbedCookie));
|
||||
memset(cookie, 0, sizeof(MbedCookie));
|
||||
cookie->fd = fd;
|
||||
cookie->serverside = true;
|
||||
|
||||
/* Initialise MbedTLS */
|
||||
mbedtls_net_init(&cookie->serverFD);
|
||||
mbedtls_ssl_init(&cookie->ssl);
|
||||
mbedtls_ssl_config_init(&cookie->conf);
|
||||
mbedtls_x509_crt_init(&cookie->cert);
|
||||
mbedtls_ctr_drbg_init(&cookie->ctrDrbg);
|
||||
mbedtls_pk_init(&cookie->serverkey);
|
||||
|
||||
mbedtls_entropy_init(&cookie->entropy);
|
||||
err = mbedtls_ctr_drbg_seed(
|
||||
&cookie->ctrDrbg,
|
||||
mbedtls_entropy_func,
|
||||
&cookie->entropy,
|
||||
(const unsigned char *) key, strlen(key)
|
||||
);
|
||||
if (err != 0)
|
||||
{
|
||||
Log(LOG_ERR, "MbedTLS failure on server init: %d", err);
|
||||
goto error;
|
||||
}
|
||||
/* Add a source of entropy if possible(using the CYTO_TLS_SEED env).
|
||||
* Note that we ignore the error code. */
|
||||
seed = getenv("CYTO_TLS_SEED");
|
||||
mbedtls_entropy_update_seed_file(&cookie->entropy, seed);
|
||||
|
||||
/* Setup key verification */
|
||||
if ((err = mbedtls_x509_crt_parse_file(&cookie->cert, crt)) != 0)
|
||||
{
|
||||
char message[256];
|
||||
mbedtls_strerror(err, message, 255);
|
||||
Log(LOG_ERR, "MbedTLS failure on server certs: %s", message);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((err = mbedtls_pk_parse_keyfile(&cookie->serverkey, key, NULL)) != 0)
|
||||
{
|
||||
char message[256];
|
||||
mbedtls_strerror(err, message, 255);
|
||||
Log(LOG_ERR, "MbedTLS failure on server certs: %s", message);
|
||||
goto error;
|
||||
}
|
||||
mbedtls_ssl_conf_ca_chain(&cookie->conf, cookie->cert.next, NULL);
|
||||
if ((err = mbedtls_ssl_conf_own_cert(&cookie->conf, &cookie->cert, &cookie->serverkey)) != 0)
|
||||
{
|
||||
char message[256];
|
||||
mbedtls_strerror(err, message, 255);
|
||||
Log(LOG_ERR, "MbedTLS failure on server certs: %s", message);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Setup SSL */
|
||||
cookie->serverFD.fd = fd;
|
||||
err = mbedtls_ssl_config_defaults(
|
||||
&cookie->conf,
|
||||
MBEDTLS_SSL_IS_SERVER,
|
||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT
|
||||
);
|
||||
if (err != 0)
|
||||
{
|
||||
char message[256];
|
||||
mbedtls_strerror(err, message, 255);
|
||||
Log(LOG_ERR, "MbedTLS failure on server SSL: %s", message);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Setup some callbacks */
|
||||
mbedtls_ssl_conf_rng(
|
||||
&cookie->conf,
|
||||
mbedtls_ctr_drbg_random,
|
||||
&cookie->ctrDrbg
|
||||
);
|
||||
mbedtls_ssl_conf_dbg(&cookie->conf, my_debug, stdout);
|
||||
mbedtls_ssl_conf_ca_chain(&cookie->conf, cookie->cert.next, NULL);
|
||||
if ((err = mbedtls_ssl_setup(&cookie->ssl, &cookie->conf)) != 0)
|
||||
{
|
||||
char message[256];
|
||||
mbedtls_strerror(err, message, 255);
|
||||
Log(LOG_ERR, "MbedTLS failure on SSL setup: %s", message);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Client connexion */
|
||||
/* Setup some functions */
|
||||
mbedtls_ssl_set_bio(
|
||||
&cookie->ssl, &cookie->serverFD,
|
||||
mbedtls_net_send, mbedtls_net_recv, NULL
|
||||
);
|
||||
|
||||
/* Handshake the client */
|
||||
while ((err = mbedtls_ssl_handshake(&cookie->ssl)) != 0)
|
||||
{
|
||||
switch (err)
|
||||
{
|
||||
case MBEDTLS_ERR_SSL_WANT_WRITE:
|
||||
case MBEDTLS_ERR_SSL_WANT_READ:
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
return cookie;
|
||||
error:
|
||||
|
||||
mbedtls_net_free(&cookie->serverFD);
|
||||
mbedtls_ssl_free(&cookie->ssl);
|
||||
mbedtls_ssl_config_free(&cookie->conf);
|
||||
mbedtls_ctr_drbg_free(&cookie->ctrDrbg);
|
||||
mbedtls_entropy_free(&cookie->entropy);
|
||||
Free(cookie);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
TlsRead(void *cookie, void *buf, size_t nBytes)
|
||||
{
|
||||
MbedCookie *cooked = cookie;
|
||||
int ret;
|
||||
if (!cookie || !buf)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ret = mbedtls_ssl_read(&cooked->ssl, buf, nBytes);
|
||||
if (ret <= 0)
|
||||
{
|
||||
ret = -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
TlsWrite(void *cookie, void *buf, size_t nBytes)
|
||||
{
|
||||
MbedCookie *cooked = cookie;
|
||||
int ret;
|
||||
if (!cookie || !buf)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ret = mbedtls_ssl_write(&cooked->ssl, buf, nBytes);
|
||||
if (ret <= 0)
|
||||
{
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
TlsClose(void *cookie)
|
||||
{
|
||||
MbedCookie *cooked = cookie;
|
||||
if (!cookie)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
mbedtls_net_free(&cooked->serverFD);
|
||||
mbedtls_ssl_free(&cooked->ssl);
|
||||
mbedtls_ssl_config_free(&cooked->conf);
|
||||
mbedtls_ctr_drbg_free(&cooked->ctrDrbg);
|
||||
mbedtls_entropy_free(&cooked->entropy);
|
||||
mbedtls_x509_crt_free(&cooked->cert);
|
||||
|
||||
if (cooked->serverside)
|
||||
{
|
||||
mbedtls_pk_free(&cooked->serverkey);
|
||||
}
|
||||
Free(cooked);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
Forgive me for not understanding MbedTLS that well, but why does this need to be sourced from the environment? Can't we randomly generate a seed? What's the use case for using the same seed more than once?
It doesn't have to in most cases(MbedTLS will try to use things like
/dev/random
if possible) but considering how POSIX doesn't really mandate such sources to exist in the first place (and that I really wouldn't use something like theRand
API for cryptographic tasks), and I think having an optional source of extra randomness, if possible, may be worth it.I agree, and that's the real problem here. Ideally, we should use the Rand API to generate the MbedTLS seed. The fact that we can't because Rand isn't cryptographically secure is a problem of its own that, in my opinion, should be fixed first before we start relying on environment variables.
I'd like Cytoplasm to very much be link-and-forget. It shouldn't have any special runtime dependencies like this. I don't even know how we'd trust the user to set the seed properly. If the system doesn't have some source of entropy that we can draw from, then I don't know how the user could generate a seed. Because if they can do it on the machine we're running on, then so can we, and so should we. Otherwise, they'd have to generate it on a different machine and then copy it over to the machine executing this Cytoplasm code? That seems pretty impractical, particularly because every time Cytoplasm is started, (or every time this function is called?) the seed needs to be freshly generated.