#include // IWYU pragma: keep #include #include #include #include #include #include #include "ant.h" #include "ptr.h" #include "errors.h" #include "modules/tls.h" #include "modules/buffer.h" typedef struct ant_tls_context_wrap_s { ant_value_t obj; tls_context *ctx; tlsuv_private_key_t key; tlsuv_certificate_t cert; bool closed; } ant_tls_context_wrap_t; enum { TLS_CONTEXT_NATIVE_TAG = 0x544c5343u }; // TLSC static ant_value_t g_tls_context_proto = 0; static void tls_context_free(ant_tls_context_wrap_t *wrap) { if (!wrap || wrap->closed) return; wrap->closed = true; if (wrap->cert && wrap->cert->free) wrap->cert->free(wrap->cert); if (wrap->key && wrap->key->free) wrap->key->free(wrap->key); if (wrap->ctx && wrap->ctx->free_ctx) wrap->ctx->free_ctx(wrap->ctx); wrap->cert = NULL; wrap->key = NULL; wrap->ctx = NULL; } static ant_tls_context_wrap_t *tls_context_data(ant_value_t value) { if (!js_check_native_tag(value, TLS_CONTEXT_NATIVE_TAG)) return NULL; return (ant_tls_context_wrap_t *)js_get_native_ptr(value); } static bool tls_value_bytes( ant_t *js, ant_value_t value, const char **bytes_out, size_t *len_out, ant_value_t *error_out ) { ant_value_t str_value = 0; const uint8_t *buffer_bytes = NULL; if (error_out) *error_out = js_mkundef(); if (bytes_out) *bytes_out = NULL; if (len_out) *len_out = 0; if (vtype(value) == T_UNDEF || vtype(value) == T_NULL) return true; if (buffer_source_get_bytes(js, value, &buffer_bytes, len_out)) { if (bytes_out) *bytes_out = (const char *)buffer_bytes; return true; } str_value = js_tostring_val(js, value); if (is_err(str_value)) { if (error_out) *error_out = str_value; return false; } if (!bytes_out || !len_out) return true; *bytes_out = js_getstr(js, str_value, len_out); if (!*bytes_out) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS input"); return false; } return true; } static ant_value_t tls_make_error(ant_t *js, tls_context *ctx, long code, const char *fallback) { const char *message = fallback; if (ctx && ctx->strerror) { const char *detail = ctx->strerror(code); if (detail && *detail) message = detail; } return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message ? message : "TLS error"); } static ant_value_t js_tls_context_close(ant_t *js, ant_value_t *args, int nargs) { ant_tls_context_wrap_t *wrap = tls_context_data(js_getthis(js)); if (!wrap) return js_getthis(js); tls_context_free(wrap); js_set_native_ptr(wrap->obj, NULL); js_set_native_tag(wrap->obj, 0); return js_getthis(js); } static void tls_init_context_proto(ant_t *js) { if (g_tls_context_proto) return; g_tls_context_proto = js_mkobj(js); js_set(js, g_tls_context_proto, "close", js_mkfun(js_tls_context_close)); } static ant_value_t js_tls_create_context(ant_t *js, ant_value_t *args, int nargs) { ant_value_t options = nargs > 0 ? args[0] : js_mkundef(); ant_value_t obj = 0; ant_value_t error = js_mkundef(); ant_tls_context_wrap_t *wrap = NULL; ant_value_t ca_v = js_mkundef(); ant_value_t key_v = js_mkundef(); ant_value_t cert_v = js_mkundef(); ant_value_t partial_v = js_mkundef(); const char *ca = NULL; const char *key_data = NULL; const char *cert_data = NULL; size_t ca_len = 0; size_t key_len = 0; size_t cert_len = 0; int rc = 0; if (vtype(options) != T_UNDEF && vtype(options) != T_NULL && vtype(options) != T_OBJ) return js_mkerr_typed(js, JS_ERR_TYPE, "TLS context options must be an object"); tls_init_context_proto(js); if (vtype(options) == T_OBJ) { ca_v = js_get(js, options, "ca"); key_v = js_get(js, options, "key"); cert_v = js_get(js, options, "cert"); partial_v = js_get(js, options, "allowPartialChain"); } if (!tls_value_bytes(js, ca_v, &ca, &ca_len, &error)) return error; if (!tls_value_bytes(js, key_v, &key_data, &key_len, &error)) return error; if (!tls_value_bytes(js, cert_v, &cert_data, &cert_len, &error)) return error; wrap = calloc(1, sizeof(*wrap)); if (!wrap) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); wrap->ctx = default_tls_context(ca, ca_len); if (!wrap->ctx) { free(wrap); return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to create TLS context"); } if (wrap->ctx->allow_partial_chain && js_truthy(js, partial_v)) { rc = wrap->ctx->allow_partial_chain(wrap->ctx, 1); if (rc != 0) { ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to enable partial chain verification"); tls_context_free(wrap); free(wrap); return err; }} if (key_data) { if (!wrap->ctx->load_key) { tls_context_free(wrap); free(wrap); return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support loading private keys"); } rc = wrap->ctx->load_key(&wrap->key, key_data, key_len); if (rc != 0) { ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to load TLS private key"); tls_context_free(wrap); free(wrap); return err; } if (cert_data) { if (!wrap->ctx->load_cert) { tls_context_free(wrap); free(wrap); return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support loading certificates"); } rc = wrap->ctx->load_cert(&wrap->cert, cert_data, cert_len); if (rc != 0) { ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to load TLS certificate"); tls_context_free(wrap); free(wrap); return err; }} if (!wrap->ctx->set_own_cert) { tls_context_free(wrap); free(wrap); return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support own certificate configuration"); } rc = wrap->ctx->set_own_cert(wrap->ctx, wrap->key, wrap->cert); if (rc != 0) { ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to configure TLS certificate"); tls_context_free(wrap); free(wrap); return err; } } else if (cert_data) { tls_context_free(wrap); free(wrap); return js_mkerr_typed(js, JS_ERR_TYPE, "TLS certificate requires a private key"); } obj = js_mkobj(js); js_set_proto_init(obj, g_tls_context_proto); wrap->obj = obj; js_set_native_ptr(obj, wrap); js_set_native_tag(obj, TLS_CONTEXT_NATIVE_TAG); return obj; } static ant_value_t js_tls_is_context(ant_t *js, ant_value_t *args, int nargs) { ant_tls_context_wrap_t *wrap = nargs > 0 ? tls_context_data(args[0]) : NULL; return js_bool(wrap && !wrap->closed && wrap->ctx); } static ant_value_t js_tls_set_config_path(ant_t *js, ant_value_t *args, int nargs) { ant_value_t str_value = js_mkundef(); const char *path = NULL; int rc = 0; if (nargs < 1 || vtype(args[0]) == T_UNDEF || vtype(args[0]) == T_NULL) { rc = tlsuv_set_config_path(NULL); if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc)); return js_mkundef(); } str_value = js_tostring_val(js, args[0]); if (is_err(str_value)) return str_value; path = js_getstr(js, str_value, NULL); if (!path) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS config path"); rc = tlsuv_set_config_path(path); if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc)); return js_mkundef(); } ant_value_t internal_tls_library(ant_t *js) { ant_value_t lib = js_mkobj(js); ant_value_t version = js_mkstr(js, "unknown", 7); tls_context *ctx = default_tls_context(NULL, 0); const char *version_str = NULL; if (ctx && ctx->version) { version_str = ctx->version(); if (version_str) version = js_mkstr(js, version_str, strlen(version_str)); } if (ctx && ctx->free_ctx) ctx->free_ctx(ctx); tls_init_context_proto(js); js_set(js, lib, "version", version); js_set(js, lib, "createContext", js_mkfun(js_tls_create_context)); js_set(js, lib, "isContext", js_mkfun(js_tls_is_context)); js_set(js, lib, "setConfigPath", js_mkfun(js_tls_set_config_path)); return lib; }