#include 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; }