forked from enlightenment/efl
308 lines
9.4 KiB
C
308 lines
9.4 KiB
C
#include <gnutls/gnutls.h>
|
|
|
|
struct _Efl_Net_Ssl_Ctx {
|
|
gnutls_certificate_credentials_t x509_cred;
|
|
gnutls_priority_t priority;
|
|
Eina_Bool is_dialer;
|
|
};
|
|
|
|
static Eina_Error
|
|
_efl_net_ssl_ctx_load_lists(Efl_Net_Ssl_Ctx *ctx, Efl_Net_Ssl_Ctx_Config cfg)
|
|
{
|
|
Eina_List *n, *n_next, *pk_node;
|
|
const char *path;
|
|
unsigned certificates_count = eina_list_count(*cfg.certificates);
|
|
unsigned private_keys_count = eina_list_count(*cfg.private_keys);
|
|
unsigned certificate_revocation_lists_count = eina_list_count(*cfg.certificate_revocation_lists);
|
|
unsigned certificate_authorities_count = eina_list_count(*cfg.certificate_authorities);
|
|
int r;
|
|
|
|
ctx->is_dialer = cfg.is_dialer;
|
|
|
|
if (cfg.load_defaults)
|
|
{
|
|
r = gnutls_certificate_set_x509_system_trust(ctx->x509_cred);
|
|
if (r < 0)
|
|
{
|
|
ERR("ssl_ctx=%p could not load default paths: %s", ctx, gnutls_strerror(r));
|
|
return ENOSYS;
|
|
}
|
|
DBG("ssl_ctx=%p loaded default paths", ctx);
|
|
}
|
|
else
|
|
DBG("ssl_ctx=%p did not load default paths", ctx);
|
|
|
|
/* GNUTLS needs certificate-key pairs, so we do:
|
|
*
|
|
* - if no private keys, use certificate as its own key;
|
|
*
|
|
* - if a private keys, walk the list alongside certificates, but
|
|
* do NOT delete elements if list sizes are different. Stop at
|
|
* last private key, allowing a single private key for multiple
|
|
* certificates.
|
|
*/
|
|
pk_node = *cfg.private_keys;
|
|
EINA_LIST_FOREACH_SAFE(*cfg.certificates, n, n_next, path)
|
|
{
|
|
const char *key = pk_node ? pk_node->data : path;
|
|
|
|
r = gnutls_certificate_set_x509_key_file(ctx->x509_cred, path, key, GNUTLS_X509_FMT_PEM);
|
|
if (r < 0)
|
|
{
|
|
ERR("ssl_ctx=%p could not use certificate from '%s' with key '%s': %s",
|
|
ctx, path, key, gnutls_strerror(r));
|
|
|
|
if (pk_node)
|
|
{
|
|
if (eina_list_count(*cfg.private_keys) == eina_list_count(*cfg.certificates))
|
|
{
|
|
pk_node = pk_node->next;
|
|
eina_stringshare_del(key);
|
|
*cfg.private_keys = eina_list_remove_list(*cfg.private_keys, pk_node->prev);
|
|
}
|
|
else if (pk_node->next) pk_node = pk_node->next;
|
|
}
|
|
|
|
eina_stringshare_del(path);
|
|
*cfg.certificates = eina_list_remove_list(*cfg.certificates, n);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (pk_node->next) pk_node = pk_node->next;
|
|
}
|
|
|
|
DBG("ssl_ctx=%p loaded certificate '%s' with key '%s'", ctx, path, key);
|
|
}
|
|
if (certificates_count && !*cfg.certificates)
|
|
{
|
|
ERR("ssl_ctx=%p none of the required certificates were loaded!", ctx);
|
|
return EINVAL;
|
|
}
|
|
|
|
if (private_keys_count && !*cfg.private_keys)
|
|
{
|
|
ERR("ssl_ctx=%p none of the required private keys were loaded!", ctx);
|
|
return EINVAL;
|
|
}
|
|
else if (pk_node != eina_list_last(*cfg.private_keys))
|
|
{
|
|
do
|
|
{
|
|
n = pk_node->next;
|
|
path = n->data;
|
|
ERR("ssl_ctx=%p extra private key is unused '%s'", ctx, path);
|
|
eina_stringshare_del(path);
|
|
*cfg.private_keys = eina_list_remove_list(*cfg.private_keys, n);
|
|
}
|
|
while (pk_node->next);
|
|
}
|
|
|
|
EINA_LIST_FOREACH_SAFE(*cfg.certificate_revocation_lists, n, n_next, path)
|
|
{
|
|
r = gnutls_certificate_set_x509_crl_file(ctx->x509_cred, path, GNUTLS_X509_FMT_PEM);
|
|
if (r < 0)
|
|
{
|
|
ERR("ssl_ctx=%p could not use certificate revocation lists from %s: %s",
|
|
ctx, path, gnutls_strerror(r));
|
|
eina_stringshare_del(path);
|
|
*cfg.certificate_revocation_lists = eina_list_remove_list(*cfg.certificate_revocation_lists, n);
|
|
continue;
|
|
}
|
|
|
|
DBG("ssl_ctx=%p loaded certificate revocation lists '%s'", ctx, path);
|
|
}
|
|
if (certificate_revocation_lists_count && !*cfg.certificate_revocation_lists)
|
|
{
|
|
ERR("ssl_ctx=%p none of the required certificate revocation lists were loaded!", ctx);
|
|
return EINVAL;
|
|
}
|
|
|
|
EINA_LIST_FOREACH_SAFE(*cfg.certificate_authorities, n, n_next, path)
|
|
{
|
|
struct stat st;
|
|
|
|
r = 0;
|
|
if (stat(path, &st) != 0)
|
|
{
|
|
ERR("ssl_ctx=%p could not load certificate authorities from '%s': %s", ctx, path, eina_error_msg_get(errno));
|
|
eina_stringshare_del(path);
|
|
*cfg.certificate_authorities = eina_list_remove_list(*cfg.certificate_authorities, n);
|
|
continue;
|
|
}
|
|
else if (S_ISDIR(st.st_mode))
|
|
r = gnutls_certificate_set_x509_trust_dir(ctx->x509_cred, path, GNUTLS_X509_FMT_PEM);
|
|
else
|
|
r = gnutls_certificate_set_x509_trust_file(ctx->x509_cred, path, GNUTLS_X509_FMT_PEM);
|
|
|
|
if (r < 0)
|
|
{
|
|
ERR("ssl_ctx=%p could not use certificate authorities from '%s': %s", ctx, path, gnutls_strerror(r));
|
|
eina_stringshare_del(path);
|
|
*cfg.certificate_authorities = eina_list_remove_list(*cfg.certificate_authorities, n);
|
|
continue;
|
|
}
|
|
|
|
DBG("ssl_ctx=%p loaded certificate authorities '%s'", ctx, path);
|
|
}
|
|
if (certificate_authorities_count && !*cfg.certificate_authorities)
|
|
{
|
|
ERR("ssl_ctx=%p none of the required certificate authorities were loaded!", ctx);
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *
|
|
efl_net_ssl_ctx_connection_new(Efl_Net_Ssl_Ctx *ctx)
|
|
{
|
|
gnutls_session_t session;
|
|
int r;
|
|
|
|
r = gnutls_init(&session, ctx->is_dialer ? GNUTLS_CLIENT : GNUTLS_SERVER);
|
|
if (r < 0)
|
|
{
|
|
ERR("ssl_ctx=%p could not create %s session: %s",
|
|
ctx, ctx->is_dialer ? "dialer" : "server", gnutls_strerror(r));
|
|
return NULL;
|
|
}
|
|
|
|
if (!ctx->priority)
|
|
{
|
|
r = gnutls_set_default_priority(session);
|
|
if (r < 0)
|
|
{
|
|
ERR("ssl_ctx=%p could not set default cipher priority: %s", ctx, gnutls_strerror(r));
|
|
goto error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
r = gnutls_priority_set(session, ctx->priority);
|
|
if (r < 0)
|
|
{
|
|
ERR("ssl_ctx=%p could not set cipher priority: %s", ctx, gnutls_strerror(r));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
r = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, ctx->x509_cred);
|
|
if (r < 0)
|
|
{
|
|
ERR("ssl_ctx=%p could not set session credentials: %s", ctx, gnutls_strerror(r));
|
|
goto error;
|
|
}
|
|
|
|
return session;
|
|
|
|
error:
|
|
gnutls_deinit(session);
|
|
return NULL;
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_ctx_setup(Efl_Net_Ssl_Ctx *ctx, Efl_Net_Ssl_Ctx_Config cfg)
|
|
{
|
|
Eina_Error err;
|
|
const char *priority;
|
|
int r;
|
|
|
|
EINA_SAFETY_ON_TRUE_RETURN_VAL(ctx->x509_cred != NULL, EALREADY);
|
|
|
|
switch (cfg.cipher)
|
|
{
|
|
case EFL_NET_SSL_CIPHER_AUTO:
|
|
priority = NULL;
|
|
break;
|
|
case EFL_NET_SSL_CIPHER_TLSV1:
|
|
priority = "NORMAL:%VERIFY_ALLOW_X509_V1_CA_CRT:!VERS-SSL3.0!VERS-TLS1.1:!VERS-TLS1.2";
|
|
break;
|
|
case EFL_NET_SSL_CIPHER_TLSV1_1:
|
|
priority = "NORMAL:%VERIFY_ALLOW_X509_V1_CA_CRT:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.2";
|
|
break;
|
|
case EFL_NET_SSL_CIPHER_TLSV1_2:
|
|
priority = "NORMAL:%VERIFY_ALLOW_X509_V1_CA_CRT:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.1";
|
|
break;
|
|
default:
|
|
ERR("ssl_ctx=%p unsupported cipher %d", ctx, cfg.cipher);
|
|
return EINVAL;
|
|
}
|
|
|
|
if (priority)
|
|
{
|
|
const char *err_pos = NULL;
|
|
r = gnutls_priority_init(&ctx->priority, priority, &err_pos);
|
|
if (r < 0)
|
|
{
|
|
size_t off = err_pos - priority;
|
|
if (r == GNUTLS_E_INVALID_REQUEST)
|
|
{
|
|
ERR("ssl_ctx=%p invalid syntax on GNUTLS priority string offset %zd: '%s'", ctx, off, priority);
|
|
return EINVAL;
|
|
}
|
|
ERR("ssl_ctx=%p could not set GNUTLS priority offset %zd '%s': %s", ctx, off, priority, gnutls_strerror(r));
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
r = gnutls_certificate_allocate_credentials(&ctx->x509_cred);
|
|
if (r < 0)
|
|
{
|
|
ERR("ssl_ctx=%p could not allocate X509 credentials: %s", ctx, gnutls_strerror(r));
|
|
err = ENOSYS;
|
|
goto err_cert_alloc;
|
|
}
|
|
|
|
err = _efl_net_ssl_ctx_load_lists(ctx, cfg);
|
|
if (err)
|
|
{
|
|
ERR("ssl_ctx=%p failed to load certificate, private keys, CRL or CA", ctx);
|
|
goto err_load;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_load:
|
|
gnutls_certificate_free_credentials(ctx->x509_cred);
|
|
ctx->x509_cred = NULL;
|
|
err_cert_alloc:
|
|
gnutls_priority_deinit(ctx->priority);
|
|
ctx->priority = NULL;
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
efl_net_ssl_ctx_teardown(Efl_Net_Ssl_Ctx *ctx)
|
|
{
|
|
if (ctx->x509_cred)
|
|
{
|
|
gnutls_certificate_free_credentials(ctx->x509_cred);
|
|
ctx->x509_cred = NULL;
|
|
}
|
|
|
|
if (ctx->priority)
|
|
{
|
|
gnutls_priority_deinit(ctx->priority);
|
|
ctx->priority = NULL;
|
|
}
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_ctx_verify_mode_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, Efl_Net_Ssl_Verify_Mode verify_mode EINA_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_ctx_hostname_verify_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, Eina_Bool hostname_verify EINA_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_ctx_hostname_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, const char *hostname EINA_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|