Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304#include <compat.h> // IWYU pragma: keep
#include <stdbool.h>#include <stdint.h>#include <stdlib.h>#include <string.h>
#ifdef _WIN32#include <winsock2.h>#include <ws2tcpip.h>#else#include <arpa/inet.h>#include <netdb.h>#endif
#if defined(__APPLE__)#include <dlfcn.h>#elif defined(_WIN32)#include <wincrypt.h>#else#include <unistd.h>#endif
#include <openssl/base64.h>#include <openssl/err.h>#include <openssl/pem.h>#include <openssl/ssl.h>#include <openssl/x509.h>
#include <tlsuv/tlsuv.h>#include <tlsuv/listener.h>#include <tlsuv/tls_engine.h>
#include "ant.h"#include "cfunc.h"#include "list.h"#include "internal.h"#include "ptr.h"#include "errors.h"#include "descriptors.h"
#include "gc/modules.h"#include "sandbox/policy.h"#include "modules/tls.h"#include "modules/buffer.h"#include "modules/events.h"#include "modules/net.h"#include "modules/stream.h"#include "modules/timer.h"#include "silver/call.h"
typedef struct ant_tls_context_wrap_s { ant_value_t obj; tls_context *ctx; tlsuv_private_key_t key; tlsuv_certificate_t cert; unsigned refs; bool closed;} ant_tls_context_wrap_t;
typedef struct tls_read_chunk_s { struct tls_read_chunk_s *next; char *data; size_t len; size_t off;} tls_read_chunk_t;
typedef struct tls_write_req_s { uv_write_t req; ant_tls_socket_t *socket; struct tls_write_req_s *next; ant_value_t callback; char *data; size_t len;} tls_write_req_t;
struct ant_tls_socket_s { ant_t *js; ant_value_t obj; ant_value_t encoding; tlsuv_stream_t stream; uv_connect_t connect_req; tls_context *ctx; ant_tls_context_wrap_t *ctx_wrap; ant_value_t secure_context; char *host; char *servername; int port; char **alpn_protocols; int alpn_count; tls_read_chunk_t *read_head; tls_read_chunk_t *read_tail; size_t read_len; tls_write_req_t *writes; ant_tls_server_t *server; const char *family; ant_tls_socket_t *next; ant_tls_socket_t **pprev; uint64_t timeout_ms; uint64_t bytes_read; uint64_t bytes_written; bool owns_ctx; bool authorized; bool connecting; bool destroyed; bool closing; bool had_error; bool read_ended; bool write_ended; bool read_drain_scheduled;};
struct ant_tls_server_s { ant_t *js; ant_value_t obj; ant_value_t secure_context; ant_tls_context_wrap_t *ctx_wrap; tlsuv_listener_t listener; char **alpn_protocols; int alpn_count; int connections; ant_tls_server_t *next; ant_tls_server_t **pprev; bool request_cert; bool listening; bool closing; bool listener_open; bool unref;};
static constexpr uint32_t TLS_CONTEXT_NATIVE_TAG = Ant_NativeTag("TLSC");static constexpr uint32_t TLS_SOCKET_NATIVE_TAG = Ant_NativeTag("TLSK");static constexpr uint32_t TLS_SERVER_NATIVE_TAG = Ant_NativeTag("TLSS");
static void tls_context_dispose(ant_tls_context_wrap_t *wrap) { if (!wrap) return;
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 void tls_context_free(ant_tls_context_wrap_t *wrap) { if (!wrap || wrap->closed) return; wrap->closed = true; if (wrap->refs > 0) return; tls_context_dispose(wrap);}
static void tls_context_retain(ant_tls_context_wrap_t *wrap) { if (wrap) wrap->refs++;}
static void tls_context_release(ant_tls_context_wrap_t *wrap) { if (!wrap || wrap->refs == 0) return; wrap->refs--; if (wrap->refs == 0 && wrap->closed) tls_context_dispose(wrap);}
static ant_tls_context_wrap_t *tls_context_data(ant_value_t value) { return (ant_tls_context_wrap_t *)js_get_native(value, TLS_CONTEXT_NATIVE_TAG);}
static ant_tls_socket_t *tls_socket_data(ant_value_t value) { return (ant_tls_socket_t *)js_get_native(value, TLS_SOCKET_NATIVE_TAG);}
static void tls_add_active_socket(ant_tls_socket_t *socket) { if (socket) Ant_List_Insert(&socket->js->tls_sockets, socket);}
static void tls_remove_active_socket(ant_tls_socket_t *socket) { Ant_List_Remove(socket);}
static bool tls_emit(ant_t *js, ant_value_t target, const char *event, ant_value_t *args, int nargs) { return eventemitter_emit_args(js, target, event, args, nargs);}
static ant_tls_socket_t *tls_require_socket(ant_t *js, ant_value_t this_val) { ant_tls_socket_t *socket = tls_socket_data(this_val); if (!socket) { js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS socket"); return NULL; } return socket;}
static ant_value_t tls_make_buffer_chunk(ant_t *js, const char *data, size_t len) { ArrayBufferData *ab = create_array_buffer_data(js, len); if (!ab) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); if (len > 0 && data) memcpy(ab->data, data, len); return create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, js->builtins.buffer_proto);}
static void tls_socket_sync_state(ant_tls_socket_t *socket) { ant_t *js = NULL; const char *ready_state = "open";
if (!socket || !is_object_type(socket->obj)) return; js = socket->js; if (socket->destroyed) ready_state = "closed"; else if (socket->connecting) ready_state = "opening";
js_set(js, socket->obj, "encrypted", js_true); js_set(js, socket->obj, "authorized", js_bool(socket->authorized)); js_set(js, socket->obj, "authorizationError", js_mknull()); js_set(js, socket->obj, "secureConnecting", js_bool(socket->connecting)); js_set(js, socket->obj, "connecting", js_bool(socket->connecting)); js_set(js, socket->obj, "destroyed", js_bool(socket->destroyed)); js_set(js, socket->obj, "readable", js_bool(!socket->destroyed)); js_set(js, socket->obj, "writable", js_bool(!socket->destroyed)); js_set(js, socket->obj, "writableNeedDrain", js_false); js_set(js, socket->obj, "readyState", js_mkstr(js, ready_state, strlen(ready_state))); js_set(js, socket->obj, "timeout", js_mknum((double)socket->timeout_ms)); js_set(js, socket->obj, "bytesRead", js_mknum((double)socket->bytes_read)); js_set(js, socket->obj, "bytesWritten", js_mknum((double)socket->bytes_written)); js_set(js, socket->obj, "remoteAddress", socket->host ? js_mkstr(js, socket->host, strlen(socket->host)) : js_mkundef()); js_set(js, socket->obj, "remotePort", socket->port > 0 ? js_mknum((double)socket->port) : js_mkundef()); js_set(js, socket->obj, "remoteFamily", js_mkstr(js, socket->family, strlen(socket->family)));}
static void tls_define_default_socket_state(ant_t *js, ant_value_t obj) { if (!is_object_type(obj)) return; if (vtype(js_get(js, obj, "encrypted")) == kTypeUndefined) js_set(js, obj, "encrypted", js_true); if (vtype(js_get(js, obj, "authorized")) == kTypeUndefined) js_set(js, obj, "authorized", js_true); if (vtype(js_get(js, obj, "authorizationError")) == kTypeUndefined) js_set(js, obj, "authorizationError", js_mknull()); if (vtype(js_get(js, obj, "secureConnecting")) == kTypeUndefined) js_set(js, obj, "secureConnecting", js_false);}
static bool tls_socket_push_read( ant_tls_socket_t *socket, const char *data, size_t len, bool front) { tls_read_chunk_t *chunk = NULL;
if (!socket || !data || len == 0) return true; chunk = calloc(1, sizeof(*chunk)); if (!chunk) return false; chunk->data = malloc(len); if (!chunk->data) { free(chunk); return false; } memcpy(chunk->data, data, len); chunk->len = len;
if (front) { chunk->next = socket->read_head; socket->read_head = chunk; if (!socket->read_tail) socket->read_tail = chunk; } else { if (socket->read_tail) socket->read_tail->next = chunk; else socket->read_head = chunk; socket->read_tail = chunk; } socket->read_len += len; return true;}
static void tls_socket_free_read_queue(ant_tls_socket_t *socket) { tls_read_chunk_t *chunk = socket ? socket->read_head : NULL; while (chunk) { tls_read_chunk_t *next = chunk->next; free(chunk->data); free(chunk); chunk = next; } if (socket) { socket->read_head = NULL; socket->read_tail = NULL; socket->read_len = 0; }}
static void tls_free_alpn(char ***protocols, int *count) { if (!*protocols) return; for (int i = 0; i < *count; i++) free((*protocols)[i]); free(*protocols); *protocols = NULL; *count = 0;}
static void tls_server_maybe_release(ant_tls_server_t *server) { bool emit_close = server->closing; ant_t *js = server->js;
if (server->listener_open || server->connections > 0) return; server->closing = false; Ant_List_Remove(server); if (!emit_close) return;
GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, server->obj); tls_emit(js, server->obj, "close", NULL, 0); GC_ROOT_RESTORE(js, root_mark);}
static void tls_server_socket_closed(ant_tls_server_t *server) { server->connections--; tls_server_maybe_release(server);}
static void tls_socket_release(ant_tls_socket_t *socket) { ant_tls_server_t *server = socket->server;
tls_remove_active_socket(socket); tls_socket_free_read_queue(socket); tls_free_alpn(&socket->alpn_protocols, &socket->alpn_count);
if (socket->ctx_wrap) tls_context_release(socket->ctx_wrap); if (socket->owns_ctx && socket->ctx && socket->ctx->free_ctx) socket->ctx->free_ctx(socket->ctx);
socket->ctx_wrap = NULL; socket->ctx = NULL; socket->owns_ctx = false; socket->secure_context = js_mkundef(); socket->encoding = js_mkundef(); socket->server = NULL; if (server) tls_server_socket_closed(server);}
static void tls_socket_free(ant_tls_socket_t *socket) { if (!socket) return; tls_socket_release(socket); if (is_object_type(socket->obj)) js_clear_native(socket->obj, TLS_SOCKET_NATIVE_TAG); free(socket->host); free(socket->servername); free(socket);}
static void tls_socket_finalize(ant_t *js, ant_object_t *obj) { ant_tls_socket_t *socket = tls_socket_data(js_obj_from_ptr(obj)); if (socket && !Ant_List_IsLinked(socket)) tls_socket_free(socket);}
static void tls_socket_close_cb(uv_handle_t *handle) { tlsuv_stream_t *tls_stream = (tlsuv_stream_t *)handle; ant_tls_socket_t *socket = tls_stream ? (ant_tls_socket_t *)tls_stream->data : NULL; ant_t *js = socket ? socket->js : NULL; ant_value_t had_error = 0; if (!socket || !js) return;
socket->destroyed = true; socket->connecting = false; socket->closing = false; tls_socket_sync_state(socket); had_error = js_bool(socket->had_error); tls_emit(js, socket->obj, "close", &had_error, 1); tls_socket_release(socket);}
static void tls_socket_close(ant_tls_socket_t *socket) { if (!socket || socket->closing || socket->destroyed) return; socket->closing = true; tlsuv_stream_close(&socket->stream, tls_socket_close_cb);}
static void tls_socket_maybe_emit_end(ant_tls_socket_t *socket) { ant_t *js = socket ? socket->js : NULL; if (!socket || !js || !socket->read_ended || socket->read_len != 0 || socket->read_head) return; tls_emit(js, socket->obj, "end", NULL, 0); tls_socket_close(socket);}
static ant_value_t tls_socket_drain_read_queue(ant_params_t) { ant_value_t obj = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); ant_tls_socket_t *socket = tls_socket_data(obj); if (!socket) return js_mkundef(); socket->read_drain_scheduled = false;
while ( !socket->destroyed && socket->read_head && eventemitter_listener_count(js, obj, "data") > 0 ) { tls_read_chunk_t *chunk = socket->read_head; size_t len = chunk->len - chunk->off; ant_value_t data = vtype(socket->encoding) == kTypeString ? js_mkstr(js, chunk->data + chunk->off, len) : tls_make_buffer_chunk(js, chunk->data + chunk->off, len); if (is_err(data)) { data = js_take_thrown(js, data); socket->had_error = true; tls_emit(js, obj, "error", &data, 1); tls_socket_close(socket); return js_mkundef(); } socket->read_head = chunk->next; if (socket->read_tail == chunk) socket->read_tail = NULL; socket->read_len -= len; free(chunk->data); free(chunk); tls_emit(js, obj, "data", &data, 1); }
tls_socket_maybe_emit_end(socket); return js_mkundef();}
static void tls_socket_schedule_read_drain(ant_tls_socket_t *socket) { if (!socket || socket->read_drain_scheduled || socket->destroyed) return; socket->read_drain_scheduled = true; queue_microtask(socket->js, js_heavy_mkfun(socket->js, tls_socket_drain_read_queue, socket->obj));}
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) == kTypeUndefined || vtype(value) == kTypeNull) 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_stream_error(ant_tls_socket_t *socket, int status, const char *fallback) { const char *message = fallback; if (socket) { const char *detail = tlsuv_stream_get_error(&socket->stream); if (detail && *detail) message = detail; else if (status < 0) message = uv_strerror(status); } return Ant_Error_Create(socket->js, JS_ERR_TYPE, message ? message : "TLS error");}
static bool tls_parse_write_args( ant_native_params_t, const uint8_t **bytes_out, size_t *len_out, ant_value_t *callback_out, ant_value_t *error_out) { ant_value_t value = 0;
if (bytes_out) *bytes_out = NULL; if (len_out) *len_out = 0; if (callback_out) *callback_out = js_mkundef(); if (error_out) *error_out = js_mkundef();
if (nargs < 1 || vtype(args[0]) == kTypeUndefined || vtype(args[0]) == kTypeNull) return true; value = args[0];
if (!buffer_source_get_bytes(js, value, bytes_out, len_out)) { size_t slen = 0; ant_value_t str_val = js_tostring_val(js, value); const char *str = NULL;
if (is_err(str_val)) { if (error_out) *error_out = str_val; return false; }
str = js_getstr(js, str_val, &slen); if (!str) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid socket write data"); return false; }
if (bytes_out) *bytes_out = (const uint8_t *)str; if (len_out) *len_out = slen; }
if (callback_out) { if (nargs > 1 && is_callable(args[1])) *callback_out = args[1]; else if (nargs > 2 && is_callable(args[2])) *callback_out = args[2]; } return true;}
static ant_value_t js_tls_context_close(ant_params_t) { ant_tls_context_wrap_t *wrap = tls_context_data(js_getthis(js)); if (!wrap) return js_getthis(js); tls_context_free(wrap); js_clear_native(wrap->obj, TLS_CONTEXT_NATIVE_TAG); return js_getthis(js);}
static void tls_init_context_proto(ant_t *js) { if (js->builtins.tls_context_proto && js->builtins.tls_context_ctor) return;
if (!js->builtins.tls_context_proto) { js->builtins.tls_context_proto = Ant_Object_CreateBare(js); js_set(js, js->builtins.tls_context_proto, "close", Ant_Function_Create(js_tls_context_close)); js_set_tostringtag(js, js->builtins.tls_context_proto, js_mkstr(js, "SecureContext", 13)); }}
static void tls_write_remove(ant_tls_socket_t *socket, tls_write_req_t *write) { tls_write_req_t **it = NULL; if (!socket || !write) return; for (it = &socket->writes; *it; it = &(*it)->next) { if (*it == write) { *it = write->next; write->next = NULL; return; }}}
static void tls_socket_maybe_close_after_writes(ant_tls_socket_t *socket) { if (socket && socket->write_ended && !socket->writes) tls_socket_close(socket);}
static void tls_socket_write_cb(uv_write_t *req, int status) { tls_write_req_t *write = (tls_write_req_t *)req; ant_tls_socket_t *socket = write ? write->socket : NULL; ant_t *js = socket ? socket->js : NULL;
if (!socket || !js) return;
tls_write_remove(socket, write); if (status < 0) { ant_value_t err = tls_stream_error(socket, status, "TLS write failed"); socket->had_error = true; tls_emit(js, socket->obj, "error", &err, 1); tls_socket_close(socket); } else { socket->bytes_written += write->len; tls_socket_sync_state(socket); if (is_callable(write->callback)) sv_call_value(js, write->callback, js_mkundef(), NULL, 0); }
free(write->data); free(write); tls_socket_maybe_close_after_writes(socket);}
static void tls_alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { (void)handle; buf->base = malloc(suggested_size); buf->len = suggested_size;}
static void tls_socket_on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { tlsuv_stream_t *tls_stream = (tlsuv_stream_t *)stream; ant_tls_socket_t *socket = tls_stream ? (ant_tls_socket_t *)tls_stream->data : NULL; ant_t *js = socket ? socket->js : NULL;
if (!socket || !js) goto done;
if (nread > 0) { ant_value_t chunk = 0; socket->bytes_read += (uint64_t)nread; tls_socket_sync_state(socket); if (eventemitter_listener_count(js, socket->obj, "data") > 0) { if (vtype(socket->encoding) == kTypeString) chunk = js_mkstr(js, buf->base, (size_t)nread); else chunk = tls_make_buffer_chunk(js, buf->base, (size_t)nread); if (is_err(chunk)) { chunk = js_take_thrown(js, chunk); socket->had_error = true; tls_emit(js, socket->obj, "error", &chunk, 1); tls_socket_close(socket); goto done; } tls_emit(js, socket->obj, "data", &chunk, 1); } else { if (!tls_socket_push_read(socket, buf->base, (size_t)nread, false)) { ant_value_t err = Ant_Error_Create(js, JS_ERR_TYPE, "Out of memory"); socket->had_error = true; tls_emit(js, socket->obj, "error", &err, 1); tls_socket_close(socket); goto done; } tls_emit(js, socket->obj, "readable", NULL, 0); } } else if (nread == UV_EOF) { socket->read_ended = true; tls_socket_maybe_emit_end(socket); } else if (nread < 0) { ant_value_t err = tls_stream_error(socket, (int)nread, "TLS read failed"); socket->had_error = true; tls_emit(js, socket->obj, "error", &err, 1); tls_socket_close(socket); }
done: free(buf ? buf->base : NULL);}
static void tls_socket_on_connect(uv_connect_t *req, int status) { ant_tls_socket_t *socket = req ? (ant_tls_socket_t *)req->data : NULL; ant_t *js = socket ? socket->js : NULL; const char *protocol = NULL;
if (!socket || !js) return; socket->connecting = false;
if (status < 0) { ant_value_t err = tls_stream_error(socket, status, "TLS connect failed"); socket->had_error = true; tls_socket_sync_state(socket); tls_emit(js, socket->obj, "error", &err, 1); tls_socket_close(socket); return; }
protocol = tlsuv_stream_get_protocol(&socket->stream); if (protocol && *protocol) js_set(js, socket->obj, "alpnProtocol", js_mkstr(js, protocol, strlen(protocol))); else js_set(js, socket->obj, "alpnProtocol", js_false);
tlsuv_stream_read_start(&socket->stream, tls_alloc_cb, tls_socket_on_read); tls_socket_sync_state(socket); tls_emit(js, socket->obj, "secureConnect", NULL, 0); tls_emit(js, socket->obj, "connect", NULL, 0);}
static ant_value_t js_tls_socket_read(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); size_t wanted = 0; char *data = NULL; size_t copied = 0; ant_value_t out = 0;
if (!socket) return Ant_Exception_Current(js); if (!socket->read_head || socket->read_len == 0) return js_mknull();
if (nargs > 0 && vtype(args[0]) == kTypeNumber && js_getnum(args[0]) > 0) wanted = (size_t)js_getnum(args[0]); if (wanted == 0 || wanted > socket->read_len) wanted = socket->read_len;
data = malloc(wanted); if (!data) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
while (copied < wanted && socket->read_head) { tls_read_chunk_t *chunk = socket->read_head; size_t available = chunk->len - chunk->off; size_t take = wanted - copied; if (take > available) take = available;
memcpy(data + copied, chunk->data + chunk->off, take); copied += take; chunk->off += take; socket->read_len -= take;
if (chunk->off >= chunk->len) { socket->read_head = chunk->next; if (socket->read_tail == chunk) socket->read_tail = NULL; free(chunk->data); free(chunk); } }
if (vtype(socket->encoding) == kTypeString) out = js_mkstr(js, data, copied); else out = tls_make_buffer_chunk(js, data, copied); free(data); tls_socket_maybe_emit_end(socket); return out;}
static ant_value_t js_tls_socket_unshift(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); const uint8_t *bytes = NULL; size_t len = 0; ant_value_t err = js_mkundef();
if (!socket) return Ant_Exception_Current(js); if (!tls_parse_write_args(js, args, nargs, &bytes, &len, NULL, &err)) return err; if (len > 0) { if (!tls_socket_push_read(socket, (const char *)bytes, len, true)) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); tls_socket_schedule_read_drain(socket); } return js_getthis(js);}
static ant_value_t js_tls_socket_write(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); const uint8_t *bytes = NULL; size_t len = 0; ant_value_t callback = js_mkundef(); ant_value_t err = js_mkundef(); tls_write_req_t *write = NULL; uv_buf_t buf; int rc = 0;
if (!socket) return Ant_Exception_Current(js); if (socket->destroyed || socket->closing) return js_false; if (!tls_parse_write_args(js, args, nargs, &bytes, &len, &callback, &err)) return err; if (len == 0) { if (is_callable(callback)) sv_call_value(js, callback, js_mkundef(), NULL, 0); return js_true; }
write = calloc(1, sizeof(*write)); if (!write) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); write->data = malloc(len); if (!write->data) { free(write); return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); } memcpy(write->data, bytes, len); write->socket = socket; write->callback = callback; write->len = len; write->next = socket->writes; socket->writes = write;
buf = uv_buf_init(write->data, (unsigned int)len); rc = tlsuv_stream_write(&write->req, &socket->stream, &buf, tls_socket_write_cb); if (rc != 0) { tls_write_remove(socket, write); free(write->data); free(write); return js_false; }
return js_true;}
static ant_value_t js_tls_socket_end(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); ant_value_t result = js_getthis(js);
if (!socket) return Ant_Exception_Current(js); if (nargs > 0 && vtype(args[0]) != kTypeUndefined && vtype(args[0]) != kTypeNull) { result = js_tls_socket_write(js, args, nargs, js_mkundef()); if (is_err(result)) return result; } socket->write_ended = true; tls_socket_maybe_close_after_writes(socket); return js_getthis(js);}
static ant_value_t js_tls_socket_destroy(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); if (!socket) return Ant_Exception_Current(js); if (socket->destroyed || socket->closing) return js_getthis(js);
if (nargs > 0 && vtype(args[0]) != kTypeUndefined && vtype(args[0]) != kTypeNull) { ant_value_t err = args[0]; socket->had_error = true; tls_emit(js, socket->obj, "error", &err, 1); }
tls_socket_close(socket); socket->destroyed = true; socket->connecting = false; tls_socket_sync_state(socket); return js_getthis(js);}
static ant_value_t js_tls_socket_pause(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); if (!socket) return Ant_Exception_Current(js); if (!socket->destroyed && !socket->closing) tlsuv_stream_read_stop(&socket->stream); return js_getthis(js);}
static ant_value_t js_tls_socket_resume(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); if (!socket) return Ant_Exception_Current(js); if (!socket->destroyed && !socket->closing) tlsuv_stream_read_start(&socket->stream, tls_alloc_cb, tls_socket_on_read); return js_getthis(js);}
static ant_value_t js_tls_socket_setEncoding(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); ant_value_t encoding = js_mkundef();
if (!socket) return Ant_Exception_Current(js); if (nargs > 0 && vtype(args[0]) != kTypeUndefined) { encoding = js_tostring_val(js, args[0]); if (is_err(encoding)) return encoding; } socket->encoding = encoding; return js_getthis(js);}
static ant_value_t js_tls_socket_setNoDelay(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); bool enable = nargs == 0 || js_truthy(js, args[0]); if (!socket) return Ant_Exception_Current(js); if (!socket->destroyed && !socket->closing) tlsuv_stream_nodelay(&socket->stream, enable ? 1 : 0); return js_getthis(js);}
static ant_value_t js_tls_socket_setKeepAlive(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); bool enable = nargs > 0 && js_truthy(js, args[0]); unsigned int delay = nargs > 1 && vtype(args[1]) == kTypeNumber ? (unsigned int)(js_getnum(args[1]) / 1000.0) : 0; if (!socket) return Ant_Exception_Current(js); if (!socket->destroyed && !socket->closing) tlsuv_stream_keepalive(&socket->stream, enable ? 1 : 0, delay); return js_getthis(js);}
static ant_value_t js_tls_socket_setTimeout(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); if (!socket) return Ant_Exception_Current(js); socket->timeout_ms = nargs > 0 && vtype(args[0]) == kTypeNumber && js_getnum(args[0]) > 0 ? (uint64_t)js_getnum(args[0]) : 0; tls_socket_sync_state(socket); if (nargs > 1 && is_callable(args[1])) eventemitter_add_listener(js, socket->obj, "timeout", args[1], true); return js_getthis(js);}
static ant_value_t tls_sockaddr_object(ant_t *js, const struct sockaddr *addr) { char host[INET6_ADDRSTRLEN] = {0}; ant_value_t out = Ant_Object_CreateBare(js); bool v6 = addr->sa_family == AF_INET6; int port = v6 ? ntohs(((const struct sockaddr_in6 *)addr)->sin6_port) : ntohs(((const struct sockaddr_in *)addr)->sin_port);
uv_ip_name(addr, host, sizeof(host)); js_set(js, out, "address", js_mkstr(js, host, strlen(host))); js_set(js, out, "family", js_mkstr(js, v6 ? "IPv6" : "IPv4", 4)); js_set(js, out, "port", js_mknum((double)port)); return out;}
static ant_value_t js_tls_socket_address(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); struct sockaddr_storage addr; int len = sizeof(addr);
if (!socket) return Ant_Exception_Current(js); if (socket->destroyed || socket->closing || socket->connecting) return Ant_Object_CreateBare(js); if (tlsuv_stream_sockname(&socket->stream, (struct sockaddr *)&addr, &len) != 0) return Ant_Object_CreateBare(js); return tls_sockaddr_object(js, (struct sockaddr *)&addr);}
static ant_value_t js_tls_socket_ref(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); if (!socket) return Ant_Exception_Current(js); if ( uv_handle_get_type((uv_handle_t *)&socket->stream.watcher) != UV_UNKNOWN_HANDLE && !uv_is_closing((uv_handle_t *)&socket->stream.watcher) ) uv_ref((uv_handle_t *)&socket->stream.watcher); return js_getthis(js);}
static ant_value_t js_tls_socket_unref(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); if (!socket) return Ant_Exception_Current(js); if ( uv_handle_get_type((uv_handle_t *)&socket->stream.watcher) != UV_UNKNOWN_HANDLE && !uv_is_closing((uv_handle_t *)&socket->stream.watcher) ) uv_unref((uv_handle_t *)&socket->stream.watcher); return js_getthis(js);}
static ant_value_t js_tls_socket_cork(ant_params_t) { return js_getthis(js);}
static ant_value_t js_tls_socket_uncork(ant_params_t) { return js_getthis(js);}
static ant_value_t js_tls_socket_getProtocol(ant_params_t) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); const char *protocol = NULL; if (!socket) return Ant_Exception_Current(js); if (socket->destroyed || socket->closing) return js_mknull(); protocol = tlsuv_stream_get_protocol(&socket->stream); return protocol && *protocol ? js_mkstr(js, protocol, strlen(protocol)) : js_mkundef();}
static ant_value_t js_tls_socket_getCipher(ant_params_t) { return js_mkundef();}
static ant_value_t js_tls_socket_getSession(ant_params_t) { return js_mkundef();}
static ant_value_t js_tls_socket_getPeerCertificate(ant_params_t) { return Ant_Object_CreateBare(js);}
static ant_value_t js_tls_socket_renegotiate(ant_params_t) { ant_value_t callback = js_mkundef(); if (nargs > 1 && is_callable(args[1])) callback = args[1]; else if (nargs > 0 && is_callable(args[0])) callback = args[0]; if (is_callable(callback)) { ant_value_t cb_args[] = {js_mknull()}; sv_call_value(js, callback, js_getthis(js), cb_args, 1); } return js_true;}
static ant_value_t js_tls_socket_ctor(ant_params_t) { ant_value_t existing = nargs > 0 ? args[0] : js_mkundef(); ant_value_t obj = 0; ant_value_t proto = 0;
tls_init_socket_proto(js); if (tls_socket_data(existing)) { tls_define_default_socket_state(js, existing); stream_init_duplex_object(js, existing, js_mkundef()); js_set_proto_wb(js, existing, js->builtins.tls_socket_proto); return existing; }
obj = Ant_Object_CreateBare(js); proto = js_instance_proto_from_new_target(js, js->builtins.tls_socket_proto, call_new_target); if (is_object_type(proto)) js_set_proto_init(obj, proto); stream_init_duplex_object(js, obj, js_mkundef()); tls_define_default_socket_state(js, obj); js_set(js, obj, "alpnProtocol", js_false); return obj;}
void tls_init_socket_proto(ant_t *js) { ant_value_t events = 0; ant_value_t ee_ctor = 0; ant_value_t ee_proto = 0; ant_value_t net = 0; ant_value_t net_socket_ctor = 0; ant_value_t net_socket_proto = 0;
if (js->builtins.tls_socket_proto && js->builtins.tls_socket_ctor) return;
if (!js->builtins.tls_socket_proto) { net = net_library(js); net_socket_ctor = js_get(js, net, "Socket"); net_socket_proto = js_get(js, net_socket_ctor, "prototype");
if (!is_object_type(net_socket_proto)) { events = events_library(js); ee_ctor = js_get(js, events, "EventEmitter"); ee_proto = js_get(js, ee_ctor, "prototype"); net_socket_proto = ee_proto; }
js->builtins.tls_socket_proto = Ant_Object_CreateBare(js); if (is_object_type(net_socket_proto)) js_set_proto_init(js->builtins.tls_socket_proto, net_socket_proto); js_set(js, js->builtins.tls_socket_proto, "read", Ant_Function_Create(js_tls_socket_read)); js_set(js, js->builtins.tls_socket_proto, "unshift", Ant_Function_Create(js_tls_socket_unshift)); js_set(js, js->builtins.tls_socket_proto, "write", Ant_Function_Create(js_tls_socket_write)); js_set(js, js->builtins.tls_socket_proto, "end", Ant_Function_Create(js_tls_socket_end)); js_set(js, js->builtins.tls_socket_proto, "destroy", Ant_Function_Create(js_tls_socket_destroy)); js_set(js, js->builtins.tls_socket_proto, "pause", Ant_Function_Create(js_tls_socket_pause)); js_set(js, js->builtins.tls_socket_proto, "resume", Ant_Function_Create(js_tls_socket_resume)); js_set(js, js->builtins.tls_socket_proto, "setEncoding", Ant_Function_Create(js_tls_socket_setEncoding)); js_set(js, js->builtins.tls_socket_proto, "setNoDelay", Ant_Function_Create(js_tls_socket_setNoDelay)); js_set(js, js->builtins.tls_socket_proto, "setKeepAlive", Ant_Function_Create(js_tls_socket_setKeepAlive)); js_set(js, js->builtins.tls_socket_proto, "setTimeout", Ant_Function_Create(js_tls_socket_setTimeout)); js_set(js, js->builtins.tls_socket_proto, "address", Ant_Function_Create(js_tls_socket_address)); js_set(js, js->builtins.tls_socket_proto, "ref", Ant_Function_Create(js_tls_socket_ref)); js_set(js, js->builtins.tls_socket_proto, "unref", Ant_Function_Create(js_tls_socket_unref)); js_set(js, js->builtins.tls_socket_proto, "cork", Ant_Function_Create(js_tls_socket_cork)); js_set(js, js->builtins.tls_socket_proto, "uncork", Ant_Function_Create(js_tls_socket_uncork)); js_set(js, js->builtins.tls_socket_proto, "getProtocol", Ant_Function_Create(js_tls_socket_getProtocol)); js_set(js, js->builtins.tls_socket_proto, "getCipher", Ant_Function_Create(js_tls_socket_getCipher)); js_set(js, js->builtins.tls_socket_proto, "getSession", Ant_Function_Create(js_tls_socket_getSession)); js_set(js, js->builtins.tls_socket_proto, "getPeerCertificate", Ant_Function_Create(js_tls_socket_getPeerCertificate)); js_set(js, js->builtins.tls_socket_proto, "renegotiate", Ant_Function_Create(js_tls_socket_renegotiate)); js_set_tostringtag(js, js->builtins.tls_socket_proto, js_mkstr(js, "TLSSocket", 9)); }
if (!js->builtins.tls_socket_ctor) js->builtins.tls_socket_ctor = Ant_Function_CreateConstructor(js, js_tls_socket_ctor, js->builtins.tls_socket_proto, "TLSSocket", 2);}
static ant_value_t js_tls_create_context(ant_params_t) { 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;
if (vtype(options) != kTypeUndefined && vtype(options) != kTypeNull && vtype(options) != kTypeObject) return js_mkerr_typed(js, JS_ERR_TYPE, "TLS context options must be an object");
tls_init_context_proto(js);
if (vtype(options) == kTypeObject) { 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;
if (cert_data && !key_data) return js_mkerr_typed(js, JS_ERR_TYPE, "TLS certificate requires a private key"); if (key_data && !cert_data) return js_mkerr_typed(js, JS_ERR_TYPE, "TLS private key requires a certificate");
wrap = calloc(1, sizeof(*wrap)); if (!wrap) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
wrap->ctx = default_tls_context(); if (!wrap->ctx) { free(wrap); return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to create TLS context"); }
if (ca && wrap->ctx->set_ca_bundle(wrap->ctx, ca, ca_len) != 0) { error = js_mkerr_typed(js, JS_ERR_TYPE, "Failed to load TLS CA bundle"); goto fail; }
if (wrap->ctx->allow_partial_chain && js_truthy(js, partial_v) && wrap->ctx->allow_partial_chain(wrap->ctx, 1) != 0) { error = js_mkerr_typed(js, JS_ERR_TYPE, "Failed to enable partial chain verification"); goto fail; }
if (!key_data) goto done;
if (!wrap->ctx->load_key) { error = js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support loading private keys"); goto fail; }
if (wrap->ctx->load_key(&wrap->key, key_data, key_len) != 0) { error = js_mkerr_typed(js, JS_ERR_TYPE, "Failed to load TLS private key: expected a PEM-encoded key"); goto fail; }
if (!wrap->ctx->load_cert) { error = js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support loading certificates"); goto fail; }
if (wrap->ctx->load_cert(&wrap->cert, cert_data, cert_len) != 0) { error = js_mkerr_typed(js, JS_ERR_TYPE, "Failed to load TLS certificate: expected a PEM-encoded certificate"); goto fail; }
if (!wrap->ctx->set_own_cert) { error = js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support own certificate configuration"); goto fail; }
if (wrap->ctx->set_own_cert(wrap->ctx, wrap->key, wrap->cert) != 0) { error = js_mkerr_typed(js, JS_ERR_TYPE, "TLS certificate does not match the private key"); goto fail; }
done: obj = Ant_Object_CreateBare(js); js_set_proto_init(obj, js->builtins.tls_context_proto); wrap->obj = obj; js_set_native(obj, wrap, TLS_CONTEXT_NATIVE_TAG);
return obj;
fail: tls_context_free(wrap); free(wrap); return error;}
static ant_value_t js_tls_is_context(ant_params_t) { 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_secure_context_ctor(ant_params_t) { return js_tls_create_context(js, args, nargs, js_mkundef());}
static ant_value_t js_tls_set_config_path(ant_params_t) { ant_value_t str_value = js_mkundef(); const char *path = NULL; int rc = 0;
if (nargs < 1 || vtype(args[0]) == kTypeUndefined || vtype(args[0]) == kTypeNull) { 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();}
static bool tls_parse_alpn(ant_t *js, ant_value_t protocols, char ***out, int *count) { ant_offset_t len = 0;
if (vtype(protocols) != kTypeArray) return true; len = js_arr_len(js, protocols); if (len <= 0) return true;
*out = calloc((size_t)len, sizeof(char *)); if (!*out) return false;
for (ant_offset_t i = 0; i < len; i++) { ant_value_t item = js_arr_get(js, protocols, i); ant_value_t str_val = js_tostring_val(js, item); const char *str = NULL; if (is_err(str_val)) return false; str = js_getstr(js, str_val, NULL); if (!str) return false; (*out)[*count] = strdup(str); if (!(*out)[*count]) return false; (*count)++; }
return true;}
static void tls_socket_attach_object(ant_t *js, ant_tls_socket_t *socket) { ant_value_t obj = Ant_Object_CreateBare(js);
js_set_proto_init(obj, js->builtins.tls_socket_proto); socket->obj = obj;
js_set_native(obj, socket, TLS_SOCKET_NATIVE_TAG); js_set_finalizer(obj, tls_socket_finalize); stream_init_duplex_object(js, obj, js_mkundef()); tls_define_default_socket_state(js, obj);
js_set(js, obj, "localAddress", js_mkundef()); js_set(js, obj, "localPort", js_mkundef()); js_set(js, obj, "localFamily", js_mkundef()); js_set(js, obj, "alpnProtocol", js_false);
tls_socket_sync_state(socket); tls_add_active_socket(socket);}
static void tls_copy_connect_option(ant_t *js, ant_value_t dst, ant_value_t src, const char *key) { ant_value_t value = js_get(js, src, key); if (vtype(value) != kTypeUndefined) js_set(js, dst, key, value);}
static void tls_copy_connect_options(ant_t *js, ant_value_t dst, ant_value_t src) { if (vtype(src) != kTypeObject) return; tls_copy_connect_option(js, dst, src, "host"); tls_copy_connect_option(js, dst, src, "hostname"); tls_copy_connect_option(js, dst, src, "port"); tls_copy_connect_option(js, dst, src, "servername"); tls_copy_connect_option(js, dst, src, "secureContext"); tls_copy_connect_option(js, dst, src, "ca"); tls_copy_connect_option(js, dst, src, "key"); tls_copy_connect_option(js, dst, src, "cert"); tls_copy_connect_option(js, dst, src, "allowPartialChain"); tls_copy_connect_option(js, dst, src, "ALPNProtocols");}
static ant_value_t tls_normalize_connect_options( ant_native_params_t, ant_value_t *callback_out) { int argc = nargs; ant_value_t options = Ant_Object_CreateBare(js);
if (callback_out) *callback_out = js_mkundef(); if (argc > 0 && is_callable(args[argc - 1])) { if (callback_out) *callback_out = args[argc - 1]; argc--; }
if (argc == 1 && vtype(args[0]) == kTypeObject) return args[0]; if (argc > 0 && vtype(args[0]) == kTypeNumber) js_set(js, options, "port", args[0]); if (argc > 1 && vtype(args[1]) == kTypeString) js_set(js, options, "host", args[1]); if (argc > 1 && vtype(args[1]) == kTypeObject) tls_copy_connect_options(js, options, args[1]); if (argc > 2 && vtype(args[2]) == kTypeObject) tls_copy_connect_options(js, options, args[2]); return options;}
static ant_value_t js_tls_connect_options(ant_t *js, ant_value_t options, ant_value_t callback) { ant_value_t obj = 0; ant_tls_socket_t *socket = NULL; ant_tls_context_wrap_t *ctx_wrap = NULL; ant_value_t value = 0; const char *host = "localhost"; const char *servername = NULL; int port = 443; int rc = 0;
if (vtype(options) != kTypeObject) return js_mkerr_typed(js, JS_ERR_TYPE, "tls.connect requires an options object");
static const char *const string_options[] = { "host", "hostname", "servername" }; for (size_t i = 0; i < sizeof(string_options) / sizeof(string_options[0]); i++) { ant_value_t nul = Ant_Error_CheckNoNullBytes(js, string_options[i], js_get(js, options, string_options[i])); if (is_err(nul)) return nul; }
value = js_get(js, options, "host"); if (vtype(value) == kTypeString) host = js_getstr(js, value, NULL); value = js_get(js, options, "hostname"); if (vtype(value) == kTypeString) host = js_getstr(js, value, NULL); value = js_get(js, options, "port"); if (vtype(value) == kTypeNumber) port = (int)js_getnum(value); value = js_get(js, options, "servername"); if (vtype(value) == kTypeString) servername = js_getstr(js, value, NULL); if (!servername || !*servername) servername = host; tls_init_socket_proto(js);
socket = calloc(1, sizeof(*socket)); if (!socket) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
socket->js = js; socket->encoding = js_mkundef(); socket->secure_context = js_mkundef(); socket->host = strdup(host ? host : "localhost"); socket->servername = strdup(servername ? servername : (host ? host : "localhost")); socket->port = port > 0 ? port : 443; socket->family = "IPv4"; socket->authorized = true; socket->connecting = true; if (!socket->host || !socket->servername) { tls_socket_free(socket); return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); }
value = js_get(js, options, "secureContext"); ctx_wrap = tls_context_data(value); if (!ctx_wrap && ( vtype(js_get(js, options, "ca")) != kTypeUndefined || vtype(js_get(js, options, "key")) != kTypeUndefined || vtype(js_get(js, options, "cert")) != kTypeUndefined)) { value = js_tls_create_context(js, &options, 1, js_mkundef()); if (is_err(value)) { tls_socket_free(socket); return value; } ctx_wrap = tls_context_data(value); } if (ctx_wrap && !ctx_wrap->closed && ctx_wrap->ctx) { socket->ctx = ctx_wrap->ctx; socket->ctx_wrap = ctx_wrap; socket->secure_context = value; socket->owns_ctx = false; tls_context_retain(ctx_wrap); } else { socket->ctx = default_tls_context(); socket->owns_ctx = true; if (!socket->ctx) { tls_socket_free(socket); return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to create TLS context"); } }
value = js_get(js, options, "ALPNProtocols"); if (!tls_parse_alpn(js, value, &socket->alpn_protocols, &socket->alpn_count)) { tls_socket_free(socket); return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to configure TLS ALPN protocols"); }
tls_socket_attach_object(js, socket); obj = socket->obj;
if (is_callable(callback)) eventemitter_add_listener(js, obj, "secureConnect", callback, true); tlsuv_stream_init(js->loop, &socket->stream, socket->ctx); socket->stream.data = socket; socket->connect_req.data = socket; if (socket->alpn_count > 0) tlsuv_stream_set_protocols(&socket->stream, socket->alpn_count, (const char **)socket->alpn_protocols);
rc = tlsuv_stream_connect(&socket->connect_req, &socket->stream, socket->host, socket->port, tls_socket_on_connect); if (rc == 0) tlsuv_stream_set_hostname(&socket->stream, socket->servername); if (rc != 0) { ant_value_t err = tls_stream_error(socket, rc, "TLS connect failed"); socket->connecting = false; socket->had_error = true; tls_socket_sync_state(socket); tls_emit(js, socket->obj, "error", &err, 1); tls_socket_close(socket); }
return obj;}
static ant_value_t js_tls_connect(ant_params_t) { ant_value_t callback = js_mkundef(); ant_value_t options = tls_normalize_connect_options(js, args, nargs, &callback); return js_tls_connect_options(js, options, callback);}
static ant_tls_server_t *tls_server_data(ant_value_t value) { return (ant_tls_server_t *)js_get_native(value, TLS_SERVER_NATIVE_TAG);}
static ant_tls_server_t *tls_require_server(ant_t *js, ant_value_t this_val) { ant_tls_server_t *server = tls_server_data(this_val); if (!server) js_mkerr_typed(js, JS_ERR_TYPE, "Invalid tls.Server"); return server;}
static void tls_server_sync_state(ant_tls_server_t *server) { if (is_object_type(server->obj)) js_set(server->js, server->obj, "listening", js_bool(server->listening));}
static void tls_server_listener_closed(uv_handle_t *handle) { ant_tls_server_t *server = tlsuv_listener_get_data((tlsuv_listener_t *)handle); server->listener_open = false; tls_server_maybe_release(server);}
static void tls_server_finalize(ant_t *js, ant_object_t *obj) { ant_value_t value = js_obj_from_ptr(obj); ant_tls_server_t *server = tls_server_data(value);
if (!server || Ant_List_IsLinked(server)) return; js_clear_native(value, TLS_SERVER_NATIVE_TAG); tls_free_alpn(&server->alpn_protocols, &server->alpn_count); tls_context_release(server->ctx_wrap); free(server);}
static tlsuv_stream_t *tls_server_on_accept(tlsuv_listener_t *l, const struct sockaddr *peer, int status) { ant_tls_server_t *server = tlsuv_listener_get_data(l); ant_t *js = server->js; ant_tls_socket_t *socket = NULL; char host[INET6_ADDRSTRLEN] = {0};
if (status != 0) { if (status != UV_EMFILE && status != UV_ENFILE) { ant_value_t err = Ant_Error_Create(js, JS_ERR_TYPE, uv_strerror(status)); tls_emit(js, server->obj, "error", &err, 1); } return NULL; }
socket = calloc(1, sizeof(*socket)); if (!socket) return NULL;
uv_ip_name(peer, host, sizeof(host)); socket->js = js; socket->encoding = js_mkundef(); socket->secure_context = js_mkundef(); socket->host = strdup(host); socket->family = peer->sa_family == AF_INET6 ? "IPv6" : "IPv4"; socket->port = peer->sa_family == AF_INET6 ? ntohs(((const struct sockaddr_in6 *)peer)->sin6_port) : ntohs(((const struct sockaddr_in *)peer)->sin_port); socket->ctx = server->ctx_wrap->ctx; socket->ctx_wrap = server->ctx_wrap; socket->authorized = server->request_cert; socket->connecting = true; socket->server = server; tls_context_retain(server->ctx_wrap); server->connections++;
if (!socket->host) { tls_socket_free(socket); return NULL; }
tls_socket_attach_object(js, socket); socket->stream.data = socket; return &socket->stream;}
static void tls_server_on_handshake(tlsuv_stream_t *clt, int status) { ant_tls_socket_t *socket = (ant_tls_socket_t *)clt->data; ant_tls_server_t *server = socket->server; ant_t *js = socket->js; const char *protocol = NULL;
socket->connecting = false; if (status != 0) { ant_value_t argv[2] = { tls_stream_error(socket, status, "TLS handshake failed"), socket->obj }; socket->had_error = true; tls_socket_sync_state(socket); if (server) tls_emit(js, server->obj, "tlsClientError", argv, 2); tls_socket_close(socket); return; }
protocol = tlsuv_stream_get_protocol(&socket->stream); if (protocol && *protocol) js_set(js, socket->obj, "alpnProtocol", js_mkstr(js, protocol, strlen(protocol)));
tlsuv_stream_read_start(&socket->stream, tls_alloc_cb, tls_socket_on_read); tls_socket_sync_state(socket); tls_emit(js, socket->obj, "secure", NULL, 0); if (server) tls_emit(js, server->obj, "secureConnection", &socket->obj, 1);}
static ant_value_t tls_server_create_context(ant_t *js, ant_value_t options, bool *request_cert) { ant_value_t value = 0; ant_value_t ctx_options = 0;
*request_cert = false; if (vtype(options) != kTypeObject) return js_tls_create_context(js, NULL, 0, js_mkundef());
*request_cert = js_truthy(js, js_get(js, options, "requestCert")); value = js_get(js, options, "secureContext"); if (tls_context_data(value)) return value;
if (*request_cert) { value = js_get(js, options, "rejectUnauthorized"); if (vtype(value) != kTypeUndefined && !js_truthy(js, value)) return js_mkerr_typed( js, JS_ERR_TYPE, "tls.Server does not support requestCert with rejectUnauthorized: false" ); if (vtype(js_get(js, options, "ca")) == kTypeUndefined) return js_mkerr_typed(js, JS_ERR_TYPE, "tls.Server requestCert requires a ca option"); }
ctx_options = Ant_Object_CreateBare(js); tls_copy_connect_option(js, ctx_options, options, "key"); tls_copy_connect_option(js, ctx_options, options, "cert"); tls_copy_connect_option(js, ctx_options, options, "allowPartialChain"); if (*request_cert) tls_copy_connect_option(js, ctx_options, options, "ca"); return js_tls_create_context(js, &ctx_options, 1, js_mkundef());}
static ant_value_t tls_server_create(ant_t *js, ant_value_t options, ant_value_t listener, ant_value_t new_target) { ant_tls_server_t *server = NULL; ant_tls_context_wrap_t *wrap = NULL; ant_value_t context = 0; ant_value_t proto = 0; ant_value_t obj = 0; bool request_cert = false;
if (vtype(options) != kTypeUndefined && vtype(options) != kTypeNull && vtype(options) != kTypeObject) return js_mkerr_typed(js, JS_ERR_TYPE, "tls.Server options must be an object");
context = tls_server_create_context(js, options, &request_cert); if (is_err(context)) return context; wrap = tls_context_data(context); if (!wrap || wrap->closed || !wrap->ctx) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS secure context");
server = calloc(1, sizeof(*server)); if (!server) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
if (vtype(options) == kTypeObject && !tls_parse_alpn(js, js_get(js, options, "ALPNProtocols"), &server->alpn_protocols, &server->alpn_count)) { tls_free_alpn(&server->alpn_protocols, &server->alpn_count); free(server); return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to configure TLS ALPN protocols"); }
tls_context_retain(wrap); if (!tls_context_data(vtype(options) == kTypeObject ? js_get(js, options, "secureContext") : js_mkundef())) tls_context_free(wrap);
obj = Ant_Object_CreateBare(js); proto = js_instance_proto_from_new_target(js, js->builtins.tls_server_proto, new_target); if (is_object_type(proto)) js_set_proto_init(obj, proto);
server->js = js; server->obj = obj; server->ctx_wrap = wrap; server->request_cert = request_cert; js_set_native(obj, server, TLS_SERVER_NATIVE_TAG); js_set_finalizer(obj, tls_server_finalize); js_set(js, obj, "maxConnections", js_mknum(0)); tls_server_sync_state(server);
if (is_callable(listener)) eventemitter_add_listener(js, obj, "secureConnection", listener, false); return obj;}
static ant_value_t js_tls_server_ctor(ant_params_t) { ant_value_t options = nargs > 0 && !is_callable(args[0]) ? args[0] : js_mkundef(); ant_value_t listener = nargs > 0 && is_callable(args[0]) ? args[0] : (nargs > 1 ? args[1] : js_mkundef()); return tls_server_create(js, options, listener, call_new_target);}
static ant_value_t js_tls_create_server(ant_params_t) { return js_tls_server_ctor(js, args, nargs, js_mkundef());}
static int tls_server_bind(ant_tls_server_t *server, const char *host, int port) { struct sockaddr_storage addr; struct addrinfo hints = {0}; struct addrinfo *res = NULL; int rc = 0;
memset(&addr, 0, sizeof(addr)); if (!host || !*host) { uv_ip6_addr("::", port, (struct sockaddr_in6 *)&addr); if (tlsuv_listener_bind(&server->listener, (struct sockaddr *)&addr, 0) == 0) return 0; uv_ip4_addr("0.0.0.0", port, (struct sockaddr_in *)&addr); return tlsuv_listener_bind(&server->listener, (struct sockaddr *)&addr, 0); }
if (uv_ip4_addr(host, port, (struct sockaddr_in *)&addr) == 0 || uv_ip6_addr(host, port, (struct sockaddr_in6 *)&addr) == 0) return tlsuv_listener_bind(&server->listener, (struct sockaddr *)&addr, 0);
hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(host, NULL, &hints, &res) != 0 || !res) return UV_EAI_NONAME;
memcpy(&addr, res->ai_addr, res->ai_addrlen); if (addr.ss_family == AF_INET6) ((struct sockaddr_in6 *)&addr)->sin6_port = htons((uint16_t)port); else ((struct sockaddr_in *)&addr)->sin_port = htons((uint16_t)port); freeaddrinfo(res);
rc = tlsuv_listener_bind(&server->listener, (struct sockaddr *)&addr, 0); return rc;}
static ant_value_t tls_server_emit_listening(ant_params_t) { ant_value_t obj = js_get_slot(js->current_func, SLOT_DATA); ant_tls_server_t *server = tls_server_data(obj); if (server && server->listening) tls_emit(js, obj, "listening", NULL, 0); return js_mkundef();}
static ant_value_t js_tls_server_listen(ant_params_t) { ant_tls_server_t *server = tls_require_server(js, js_getthis(js)); ant_value_t callback = js_mkundef(); const char *host = NULL; int port = 0; int backlog = 511; int rc = 0;
if (!server) return Ant_Exception_Current(js); if (server->listening || server->listener_open) return js_mkerr_typed(js, JS_ERR_TYPE, "Server is already listening");
if (nargs > 0 && is_callable(args[nargs - 1])) callback = args[--nargs]; if (nargs > 0 && vtype(args[0]) == kTypeObject) { ant_value_t value = js_get(js, args[0], "path"); if (vtype(value) != kTypeUndefined) return js_mkerr_typed(js, JS_ERR_TYPE, "tls.Server cannot listen on a path"); value = js_get(js, args[0], "port"); if (vtype(value) == kTypeNumber) port = (int)js_getnum(value); value = js_get(js, args[0], "host"); if (vtype(value) == kTypeString) host = js_getstr(js, value, NULL); value = js_get(js, args[0], "backlog"); if (vtype(value) == kTypeNumber && js_getnum(value) > 0) backlog = (int)js_getnum(value); } else if (nargs > 0) { if (vtype(args[0]) == kTypeString) return js_mkerr_typed(js, JS_ERR_TYPE, "tls.Server cannot listen on a path"); if (vtype(args[0]) == kTypeNumber) port = (int)js_getnum(args[0]); if (nargs > 1 && vtype(args[1]) == kTypeString) host = js_getstr(js, args[1], NULL); if (nargs > 1 && vtype(args[nargs - 1]) == kTypeNumber && js_getnum(args[nargs - 1]) > 0) backlog = (int)js_getnum(args[nargs - 1]); }
if (port < 0 || port > 65535) return js_mkerr_typed(js, JS_ERR_RANGE, "Port should be >= 0 and < 65536");
if (port == 0 && ant_sandbox_policy_forward_restricted()) return js_mkerr_typed( js, JS_ERR_TYPE | JS_ERR_NO_STACK, "sandbox listen on port 0 is not supported; use a fixed port with --forward <port>" );
if (!ant_sandbox_policy_port_forwarded(port)) return js_mkerr_typed( js, JS_ERR_TYPE | JS_ERR_NO_STACK, "sandbox listen on port %d requires --forward %d or --forward <host>:%d", port, port, port );
if (!server->ctx_wrap->ctx) return js_mkerr_typed(js, JS_ERR_TYPE, "TLS secure context is closed");
tlsuv_listener_set_data(&server->listener, server); rc = tlsuv_listener_init(js->loop, &server->listener, server->ctx_wrap->ctx); if (rc == UV_ENOTSUP) return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support servers"); if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc));
server->listener_open = true; Ant_List_Insert(&js->tls_servers, server);
if (server->alpn_count > 0) rc = tlsuv_listener_set_protocols(&server->listener, server->alpn_count, (const char **)server->alpn_protocols); if (rc == 0) rc = tls_server_bind(server, host, port); if (rc == 0) rc = tlsuv_listener_start_listen(&server->listener, backlog, tls_server_on_accept, tls_server_on_handshake);
if (rc != 0) { tlsuv_listener_close(&server->listener, tls_server_listener_closed); return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc)); }
if (server->unref) uv_unref((uv_handle_t *)&server->listener); server->listening = true; server->closing = false; tls_server_sync_state(server);
GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, callback); if (is_callable(callback)) eventemitter_add_listener(js, server->obj, "listening", callback, true); ant_value_t emit = js_make_data_cfunc(js, server->obj, tls_server_emit_listening); if (!is_err(emit)) queue_next_tick(js, emit); GC_ROOT_RESTORE(js, root_mark);
return js_getthis(js);}
static ant_value_t js_tls_server_close(ant_params_t) { ant_tls_server_t *server = tls_require_server(js, js_getthis(js)); ant_value_t callback = nargs > 0 && is_callable(args[0]) ? args[0] : js_mkundef();
if (!server) return Ant_Exception_Current(js); if (!server->listening) { if (is_callable(callback)) { ant_value_t err = Ant_Error_Create(js, JS_ERR_TYPE, "Server is not running."); js_set(js, err, "code", js_mkstr(js, "ERR_SERVER_NOT_RUNNING", 22)); sv_call_value(js, callback, js_mkundef(), &err, 1); } return js_getthis(js); }
if (is_callable(callback)) eventemitter_add_listener(js, server->obj, "close", callback, true); server->listening = false; server->closing = true; tls_server_sync_state(server); tlsuv_listener_close(&server->listener, tls_server_listener_closed); return js_getthis(js);}
static ant_value_t js_tls_server_address(ant_params_t) { ant_tls_server_t *server = tls_require_server(js, js_getthis(js)); struct sockaddr_storage addr; int len = sizeof(addr);
if (!server) return Ant_Exception_Current(js); if (!server->listening) return js_mknull(); if (tlsuv_listener_getsockname(&server->listener, (struct sockaddr *)&addr, &len) != 0) return js_mknull(); return tls_sockaddr_object(js, (struct sockaddr *)&addr);}
static ant_value_t js_tls_server_getConnections(ant_params_t) { ant_tls_server_t *server = tls_require_server(js, js_getthis(js)); ant_value_t argv[2] = { js_mknull(), js_mknum(0) };
if (!server) return Ant_Exception_Current(js); argv[1] = js_mknum((double)server->connections); if (nargs > 0 && is_callable(args[0])) sv_call_value(js, args[0], js_mkundef(), argv, 2); return js_getthis(js);}
static ant_value_t tls_server_set_ref(ant_t *js, bool ref) { ant_tls_server_t *server = tls_require_server(js, js_getthis(js));
if (!server) return Ant_Exception_Current(js); server->unref = !ref; if (server->listening && server->listener.bound) { if (ref) uv_ref((uv_handle_t *)&server->listener); else uv_unref((uv_handle_t *)&server->listener); } return js_getthis(js);}
static ant_value_t js_tls_server_ref(ant_params_t) { return tls_server_set_ref(js, true);}
static ant_value_t js_tls_server_unref(ant_params_t) { return tls_server_set_ref(js, false);}
static void tls_init_server_proto(ant_t *js) { ant_value_t net = 0; ant_value_t net_server_proto = 0; ant_value_t proto = 0;
if (js->builtins.tls_server_proto && js->builtins.tls_server_ctor) return;
net = net_library(js); net_server_proto = js_get(js, js_get(js, net, "Server"), "prototype");
proto = Ant_Object_CreateBare(js); if (is_object_type(net_server_proto)) js_set_proto_init(proto, net_server_proto); js_set(js, proto, "listen", Ant_Function_Create(js_tls_server_listen)); js_set(js, proto, "close", Ant_Function_CreateWithArity(js_tls_server_close, 1)); js_set(js, proto, "address", Ant_Function_Create(js_tls_server_address)); js_set(js, proto, "getConnections", Ant_Function_CreateWithArity(js_tls_server_getConnections, 1)); js_set(js, proto, "ref", Ant_Function_Create(js_tls_server_ref)); js_set(js, proto, "unref", Ant_Function_Create(js_tls_server_unref)); js_set_tostringtag(js, proto, js_mkstr(js, "Server", 6));
js->builtins.tls_server_proto = proto; js->builtins.tls_server_ctor = Ant_Function_CreateConstructor(js, js_tls_server_ctor, proto, "Server", 2);}
void cleanup_tls_module(ant_t *js) { for (ant_tls_server_t *server = js->tls_servers; server; server = server->next) { if (!server->listener_open || server->listener.closing) continue; server->listening = false; tlsuv_listener_close(&server->listener, tls_server_listener_closed); }
for (ant_tls_socket_t *socket = js->tls_sockets; socket; socket = socket->next) tls_socket_close(socket);}
typedef struct { char *data; size_t len; size_t cap;} tls_strbuf_t;
static void tls_strbuf_append(tls_strbuf_t *buf, const char *data, size_t len) { if (buf->len + len + 1 > buf->cap) { size_t cap = buf->cap ? buf->cap * 2 : 128; char *next = NULL; while (cap < buf->len + len + 1) cap *= 2; next = realloc(buf->data, cap); if (!next) return; buf->data = next; buf->cap = cap; } memcpy(buf->data + buf->len, data, len); buf->len += len; buf->data[buf->len] = '\0';}
static void tls_strbuf_puts(tls_strbuf_t *buf, const char *str) { tls_strbuf_append(buf, str, strlen(str));}
static bool tls_canonical_ip(const char *ip, char *out, size_t out_len) { unsigned char addr[sizeof(struct in6_addr)]; if (inet_pton(AF_INET, ip, addr) == 1) return inet_ntop(AF_INET, addr, out, (socklen_t)out_len) != NULL; if (inet_pton(AF_INET6, ip, addr) == 1) return inet_ntop(AF_INET6, addr, out, (socklen_t)out_len) != NULL; return false;}
static bool tls_host_ipv4_strict(const char *host) { int parts = 0; const char *p = host;
while (parts < 4) { unsigned value = 0; const char *start = p; while (*p >= '0' && *p <= '9') { value = value * 10 + (unsigned)(*p - '0'); if (value > 255) return false; p++; } if (p == start || (p - start > 1 && *start == '0')) return false; parts++; if (parts < 4 && *p++ != '.') return false; } return *p == '\0';}
static bool tls_host_ip(const char *host, char *out, size_t out_len) { unsigned char addr[sizeof(struct in6_addr)]; if (strchr(host, ':')) return inet_pton(AF_INET6, host, addr) == 1 && inet_ntop(AF_INET6, addr, out, (socklen_t)out_len) != NULL; return tls_host_ipv4_strict(host) && inet_pton(AF_INET, host, addr) == 1 && inet_ntop(AF_INET, addr, out, (socklen_t)out_len) != NULL;}
static bool tls_host_loose_ipv4(const char *host) { uint64_t parts[4]; int count = 0; const char *p = host;
for (;;) { uint64_t value = 0; int base = 10; const char *digits = p;
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { base = 16; p += 2; digits = p; } else if (p[0] == '0' && p[1] >= '0' && p[1] <= '9') base = 8;
for (;; p++) { int digit; if (*p >= '0' && *p <= '9') digit = *p - '0'; else if (base == 16 && *p >= 'a' && *p <= 'f') digit = *p - 'a' + 10; else if (base == 16 && *p >= 'A' && *p <= 'F') digit = *p - 'A' + 10; else break; if (digit >= base) return false; value = value * (uint64_t)base + (uint64_t)digit; if (value > 0xffffffffULL) return false; }
if (p == digits) return false; if (count == 4) return false; parts[count++] = value; if (*p == '\0') break; if (*p++ != '.') return false; }
for (int i = 0; i < count - 1; i++) if (parts[i] > 255) return false; return parts[count - 1] <= (0xffffffffULL >> (8 * (count - 1)));}
static int tls_split_host(char *host, char **parts, int max_parts) { size_t len = strlen(host); int count = 0;
if (len > 0 && host[len - 1] == '.') host[--len] = '\0'; for (size_t i = 0; i < len; i++) if (host[i] >= 'A' && host[i] <= 'Z') host[i] = (char)(host[i] - 'A' + 'a');
parts[count++] = host; for (char *p = host; *p; p++) if (*p == '.') { if (count == max_parts) return -1; *p = '\0'; parts[count++] = p + 1; } return count;}
static bool tls_host_matches(char **host_parts, int host_count, const char *pattern_in, bool wildcards) { char pattern[256]; char *parts[128]; int count = 0; const char *star = NULL; const char *prefix = NULL; const char *host_sub = host_parts[0]; size_t prefix_len = 0; size_t suffix_len = 0; size_t host_len = strlen(host_sub);
if (!pattern_in || !*pattern_in || strlen(pattern_in) >= sizeof(pattern)) return false; strcpy(pattern, pattern_in); count = tls_split_host(pattern, parts, 128); if (count != host_count) return false;
for (int i = 0; i < count; i++) { if (!*parts[i]) return false; for (const unsigned char *c = (const unsigned char *)parts[i]; *c; c++) if (*c < 0x21 || *c > 0x7f) return false; }
for (int i = count - 1; i > 0; i--) if (strcmp(host_parts[i], parts[i]) != 0) return false;
prefix = parts[0]; star = strchr(prefix, '*'); if (!star || strstr(prefix, "xn--")) return strcmp(host_sub, prefix) == 0; if (!wildcards || strchr(star + 1, '*') || count <= 2) return false;
prefix_len = (size_t)(star - prefix); suffix_len = strlen(star + 1); if (prefix_len + suffix_len > host_len) return false; return strncmp(host_sub, prefix, prefix_len) == 0 && strcmp(host_sub + host_len - suffix_len, star + 1) == 0;}
static char *tls_value_cstr(ant_t *js, ant_value_t value) { ant_value_t str = js_tostring_val(js, value); size_t len = 0; const char *data = is_err(str) ? NULL : js_getstr(js, str, &len); char *out = data ? malloc(len + 1) : NULL; if (!out) return NULL; memcpy(out, data, len); out[len] = '\0'; return out;}
static ant_value_t js_tls_check_server_identity(ant_params_t) { ant_value_t cert = nargs > 1 ? args[1] : js_mkundef(); ant_value_t subject = 0; ant_value_t cn = js_mkundef(); ant_value_t err = 0; char *hostname = NULL; char *altnames = NULL; char *host_copy = NULL; char *host_parts[128]; int host_count = 0; char canonical[INET6_ADDRSTRLEN]; bool valid = false; bool has_dns = false; tls_strbuf_t ips = {0}; tls_strbuf_t reason = {0}; tls_strbuf_t message = {0};
if (!is_object_type(cert)) return js_mkerr_typed(js, JS_ERR_TYPE, "The \"cert\" argument must be of type object");
hostname = tls_value_cstr(js, nargs > 0 ? args[0] : js_mkundef()); if (!hostname) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); if (*hostname && hostname[strlen(hostname) - 1] == '.') hostname[strlen(hostname) - 1] = '\0';
subject = js_get(js, cert, "subject"); if (is_object_type(subject)) cn = js_get(js, subject, "CN"); if (vtype(js_get(js, cert, "subjectaltname")) == kTypeString) altnames = tls_value_cstr(js, js_get(js, cert, "subjectaltname"));
host_copy = strdup(hostname); host_count = host_copy ? tls_split_host(host_copy, host_parts, 128) : -1;
for (char *entry = altnames; entry && *entry;) { char *end = entry; bool quoted = false; for (; *end; end++) { if (*end == '"' && (end == entry || end[-1] != '\\')) quoted = !quoted; else if (!quoted && end[0] == ',' && end[1] == ' ') break; } char saved = *end; *end = '\0';
if (strncmp(entry, "DNS:", 4) == 0) { has_dns = true; if (host_count > 0 && tls_host_matches(host_parts, host_count, entry + 4, true)) valid = true; } else if (strncmp(entry, "IP Address:", 11) == 0) { const char *ip = tls_canonical_ip(entry + 11, canonical, sizeof(canonical)) ? canonical : entry + 11; if (ips.len > 0) tls_strbuf_puts(&ips, ", "); tls_strbuf_puts(&ips, ip); }
*end = saved; entry = saved ? end + 2 : end; }
bool host_is_ip = tls_host_ip(hostname, canonical, sizeof(canonical)); if (!host_is_ip && tls_host_loose_ipv4(hostname)) { valid = false; tls_strbuf_puts(&reason, "Host is a non-canonical IP address"); } else if (host_is_ip) { valid = false; for (char *ip = ips.data; ip && *ip;) { char *sep = strstr(ip, ", "); size_t len = sep ? (size_t)(sep - ip) : strlen(ip); if (len == strlen(canonical) && strncmp(ip, canonical, len) == 0) valid = true; ip = sep ? sep + 2 : ip + len; } if (!valid) { tls_strbuf_puts(&reason, "IP: "); tls_strbuf_puts(&reason, hostname); tls_strbuf_puts(&reason, " is not in the cert's list: "); tls_strbuf_puts(&reason, ips.data ? ips.data : ""); } } else if (has_dns) { if (!valid) { tls_strbuf_puts(&reason, "Host: "); tls_strbuf_puts(&reason, hostname); tls_strbuf_puts(&reason, ". is not in the cert's altnames: "); tls_strbuf_puts(&reason, altnames); } } else if (js_truthy(js, cn)) { char *cn_text = tls_value_cstr(js, cn); if (vtype(cn) == kTypeArray) { ant_offset_t len = js_arr_len(js, cn); for (ant_offset_t i = 0; i < len && !valid; i++) { char *item = tls_value_cstr(js, js_arr_get(js, cn, i)); valid = item && host_count > 0 && tls_host_matches(host_parts, host_count, item, true); free(item); } } else valid = cn_text && host_count > 0 && tls_host_matches(host_parts, host_count, cn_text, true);
if (!valid) { tls_strbuf_puts(&reason, "Host: "); tls_strbuf_puts(&reason, hostname); tls_strbuf_puts(&reason, ". is not cert's CN: "); tls_strbuf_puts(&reason, cn_text ? cn_text : ""); } free(cn_text); } else { tls_strbuf_puts(&reason, "Cert does not contain a DNS name"); }
if (valid) err = js_mkundef(); else { tls_strbuf_puts(&message, "Hostname/IP does not match certificate's altnames: "); tls_strbuf_puts(&message, reason.data ? reason.data : ""); err = Ant_Error_Create(js, JS_ERR_TYPE, message.data ? message.data : "Hostname/IP mismatch"); js_set(js, err, "name", js_mkstr(js, "Error", 5)); js_set(js, err, "code", js_mkstr(js, "ERR_TLS_CERT_ALTNAME_INVALID", 28)); js_set(js, err, "reason", js_mkstr(js, reason.data ? reason.data : "", reason.len)); js_set(js, err, "host", js_mkstr(js, hostname, strlen(hostname))); js_set(js, err, "cert", cert); }
free(hostname); free(altnames); free(host_copy); free(ips.data); free(reason.data); free(message.data); return err;}
static int tls_strcmp_ptr(const void *a, const void *b) { return strcmp(*(char *const *)a, *(char *const *)b);}
static ant_value_t js_tls_get_ciphers(ant_params_t) { static const uint16_t tls13_suites[] = { 0x1301, 0x1302, 0x1303 }; SSL_CTX *ctx = SSL_CTX_new(TLS_method()); STACK_OF(SSL_CIPHER) *ciphers = ctx ? SSL_CTX_get_ciphers(ctx) : NULL; size_t count = ciphers ? sk_SSL_CIPHER_num(ciphers) : 0; size_t total = count + sizeof(tls13_suites) / sizeof(tls13_suites[0]); char **names = calloc(total, sizeof(char *)); ant_value_t out = js_mkarr(js); size_t n = 0;
for (size_t i = 0; names && i < count; i++) names[n++] = strdup(SSL_CIPHER_get_name(sk_SSL_CIPHER_value(ciphers, i))); for (size_t i = 0; names && i < sizeof(tls13_suites) / sizeof(tls13_suites[0]); i++) { const SSL_CIPHER *cipher = SSL_get_cipher_by_value(tls13_suites[i]); if (cipher) names[n++] = strdup(SSL_CIPHER_standard_name(cipher)); }
for (size_t i = 0; i < n; i++) for (char *c = names[i]; c && *c; c++) if (*c >= 'A' && *c <= 'Z') *c = (char)(*c - 'A' + 'a'); if (names) qsort(names, n, sizeof(char *), tls_strcmp_ptr);
for (size_t i = 0; i < n; i++) { if (names[i] && (i == 0 || !names[i - 1] || strcmp(names[i], names[i - 1]) != 0)) js_arr_push(js, out, js_mkstr(js, names[i], strlen(names[i]))); }
for (size_t i = 0; i < n; i++) free(names[i]); free(names); if (ctx) SSL_CTX_free(ctx); return out;}
static void tls_push_der_as_pem(ant_t *js, ant_value_t out, const uint8_t *der, size_t der_len) { static const char header[] = "-----BEGIN CERTIFICATE-----\n"; static const char footer[] = "-----END CERTIFICATE-----"; size_t b64_len = 4 * ((der_len + 2) / 3); uint8_t *b64 = malloc(b64_len + 1); tls_strbuf_t pem = {0};
if (!b64) return; EVP_EncodeBlock(b64, der, der_len); tls_strbuf_puts(&pem, header); for (size_t i = 0; i < b64_len; i += 64) { tls_strbuf_append(&pem, (const char *)b64 + i, b64_len - i < 64 ? b64_len - i : 64); tls_strbuf_puts(&pem, "\n"); } tls_strbuf_puts(&pem, footer); if (pem.data) js_arr_push(js, out, js_mkstr(js, pem.data, pem.len)); free(pem.data); free(b64);}
#if defined(__APPLE__)static void tls_load_root_certificates(ant_t *js, ant_value_t out) { void *cf = dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", RTLD_LAZY | RTLD_LOCAL); void *sec = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY | RTLD_LOCAL); int32_t (*copy_anchors)(const void **) = sec ? dlsym(sec, "SecTrustCopyAnchorCertificates") : NULL; const void *(*copy_data)(const void *) = sec ? dlsym(sec, "SecCertificateCopyData") : NULL; long (*array_count)(const void *) = cf ? dlsym(cf, "CFArrayGetCount") : NULL; const void *(*array_get)(const void *, long) = cf ? dlsym(cf, "CFArrayGetValueAtIndex") : NULL; const uint8_t *(*data_bytes)(const void *) = cf ? dlsym(cf, "CFDataGetBytePtr") : NULL; long (*data_len)(const void *) = cf ? dlsym(cf, "CFDataGetLength") : NULL; void (*release)(const void *) = cf ? dlsym(cf, "CFRelease") : NULL; const void *anchors = NULL;
if (!copy_anchors || !copy_data || !array_count || !array_get || !data_bytes || !data_len || !release) return; if (copy_anchors(&anchors) != 0 || !anchors) return;
for (long i = 0; i < array_count(anchors); i++) { const void *data = copy_data(array_get(anchors, i)); if (!data) continue; tls_push_der_as_pem(js, out, data_bytes(data), (size_t)data_len(data)); release(data); } release(anchors);}#elif defined(_WIN32)static void tls_load_root_certificates(ant_t *js, ant_value_t out) { HCERTSTORE store = CertOpenSystemStoreA(0, "ROOT"); PCCERT_CONTEXT cert = NULL;
if (!store) return; while ((cert = CertEnumCertificatesInStore(store, cert)) != NULL) tls_push_der_as_pem(js, out, cert->pbCertEncoded, cert->cbCertEncoded); CertCloseStore(store, 0);}#elsestatic void tls_load_root_certificates(ant_t *js, ant_value_t out) { static const char *const bundles[] = { "/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/certs/ca-bundle.crt", "/etc/ssl/ca-bundle.pem", "/etc/pki/tls/cacert.pem", "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", "/etc/ssl/certs/ca-bundle.crt", }; const char *path = getenv(X509_get_default_cert_file_env()); BIO *bio = NULL; X509 *cert = NULL;
if (!path || access(path, R_OK) != 0) path = X509_get_default_cert_file(); for (size_t i = 0; access(path, R_OK) != 0 && i < sizeof(bundles) / sizeof(bundles[0]); i++) path = bundles[i];
bio = BIO_new_file(path, "r"); if (!bio) return;
while ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) { uint8_t *der = NULL; int der_len = i2d_X509(cert, &der); if (der_len > 0) tls_push_der_as_pem(js, out, der, (size_t)der_len); OPENSSL_free(der); X509_free(cert); } ERR_clear_error(); BIO_free(bio);}#endif
static ant_value_t js_tls_root_certificates(ant_params_t) { ant_value_t getter = js_getcurrentfunc(js); ant_value_t cached = js_get_slot(getter, SLOT_DATA); ant_value_t out = 0;
if (vtype(cached) == kTypeArray) return cached;
out = js_mkarr(js); GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, out); tls_load_root_certificates(js, out); builtin_object_freeze(js, &out, 1, js_mkundef()); js_set_slot_wb(js, getter, SLOT_DATA, out); GC_ROOT_RESTORE(js, root_mark); return out;}
static ant_value_t tls_build_library(ant_t *js) { ant_value_t lib = Ant_Object_CreateBare(js); ant_value_t version = js_mkstr(js, "unknown", 7); ant_value_t connect = Ant_Function_Create(js_tls_connect); ant_value_t create_secure_context = Ant_Function_Create(js_tls_create_context); tls_context *ctx = default_tls_context(); 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); tls_init_socket_proto(js); tls_init_server_proto(js); if (!js->builtins.tls_context_ctor) js->builtins.tls_context_ctor = Ant_Function_CreateConstructor( js, js_tls_secure_context_ctor, js->builtins.tls_context_proto, "SecureContext", 4 ); js_set(js, lib, "version", version); js_set(js, lib, "TLSSocket", js->builtins.tls_socket_ctor); js_set(js, lib, "Server", js->builtins.tls_server_ctor); js_set(js, lib, "createServer", Ant_Function_Create(js_tls_create_server)); js_set(js, lib, "SecureContext", js->builtins.tls_context_ctor); js_set(js, lib, "createSecureContext", create_secure_context); js_set(js, lib, "connect", connect); js_set(js, lib, "createConnection", connect); js_set(js, lib, "isSecureContext", Ant_Function_Create(js_tls_is_context)); js_set(js, lib, "setConfigPath", Ant_Function_Create(js_tls_set_config_path)); js_set(js, lib, "checkServerIdentity", Ant_Function_Create(js_tls_check_server_identity)); js_set(js, lib, "getCiphers", Ant_Function_Create(js_tls_get_ciphers)); js_set_getter_desc( js, lib, "rootCertificates", 16, js_heavy_mkfun(js, js_tls_root_certificates, js_mkundef()), JS_DESC_E ); js_set(js, lib, "DEFAULT_ECDH_CURVE", js_mkstr(js, "auto", 4)); js_set(js, lib, "DEFAULT_MIN_VERSION", js_mkstr(js, "TLSv1.2", 7)); js_set(js, lib, "DEFAULT_MAX_VERSION", js_mkstr(js, "TLSv1.3", 7)); js_set(js, lib, "CLIENT_RENEG_LIMIT", js_mknum(3)); js_set(js, lib, "CLIENT_RENEG_WINDOW", js_mknum(600)); js_set(js, lib, "default", lib); js_set_tostringtag(js, lib, js_mkstr(js, "tls", 3)); return lib;}
ant_value_t tls_library(ant_t *js) { return tls_build_library(js);}
void gc_mark_tls(ant_t *js, gc_mark_fn mark) { ant_tls_socket_t *socket = NULL;
for (socket = js->tls_sockets; socket; socket = socket->next) { mark(js, socket->obj); if (vtype(socket->encoding) != kTypeUndefined) mark(js, socket->encoding); if (vtype(socket->secure_context) != kTypeUndefined) mark(js, socket->secure_context); for (tls_write_req_t *write = socket->writes; write; write = write->next) if (vtype(write->callback) != kTypeUndefined) mark(js, write->callback); }}