MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <stdbool.h>
4#include <stdint.h>
5#include <stdlib.h>
6#include <string.h>
7
8#include <tlsuv/tlsuv.h>
9#include <tlsuv/tls_engine.h>
10
11#include "ant.h"
12#include "ptr.h"
13#include "errors.h"
14
15#include "modules/tls.h"
16#include "modules/buffer.h"
17
18typedef struct ant_tls_context_wrap_s {
19 ant_value_t obj;
20 tls_context *ctx;
21 tlsuv_private_key_t key;
22 tlsuv_certificate_t cert;
23 bool closed;
24} ant_tls_context_wrap_t;
25
26enum { TLS_CONTEXT_NATIVE_TAG = 0x544c5343u }; // TLSC
27static ant_value_t g_tls_context_proto = 0;
28
29static void tls_context_free(ant_tls_context_wrap_t *wrap) {
30 if (!wrap || wrap->closed) return;
31 wrap->closed = true;
32
33 if (wrap->cert && wrap->cert->free) wrap->cert->free(wrap->cert);
34 if (wrap->key && wrap->key->free) wrap->key->free(wrap->key);
35 if (wrap->ctx && wrap->ctx->free_ctx) wrap->ctx->free_ctx(wrap->ctx);
36
37 wrap->cert = NULL;
38 wrap->key = NULL;
39 wrap->ctx = NULL;
40}
41
42static ant_tls_context_wrap_t *tls_context_data(ant_value_t value) {
43 if (!js_check_native_tag(value, TLS_CONTEXT_NATIVE_TAG)) return NULL;
44 return (ant_tls_context_wrap_t *)js_get_native_ptr(value);
45}
46
47static bool tls_value_bytes(
48 ant_t *js,
49 ant_value_t value,
50 const char **bytes_out,
51 size_t *len_out,
52 ant_value_t *error_out
53) {
54 ant_value_t str_value = 0;
55 const uint8_t *buffer_bytes = NULL;
56
57 if (error_out) *error_out = js_mkundef();
58 if (bytes_out) *bytes_out = NULL;
59 if (len_out) *len_out = 0;
60
61 if (vtype(value) == T_UNDEF || vtype(value) == T_NULL) return true;
62 if (buffer_source_get_bytes(js, value, &buffer_bytes, len_out)) {
63 if (bytes_out) *bytes_out = (const char *)buffer_bytes;
64 return true;
65 }
66
67 str_value = js_tostring_val(js, value);
68 if (is_err(str_value)) {
69 if (error_out) *error_out = str_value;
70 return false;
71 }
72
73 if (!bytes_out || !len_out) return true;
74 *bytes_out = js_getstr(js, str_value, len_out);
75
76 if (!*bytes_out) {
77 if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS input");
78 return false;
79 }
80
81 return true;
82}
83
84static ant_value_t tls_make_error(ant_t *js, tls_context *ctx, long code, const char *fallback) {
85 const char *message = fallback;
86 if (ctx && ctx->strerror) {
87 const char *detail = ctx->strerror(code);
88 if (detail && *detail) message = detail;
89 }
90 return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message ? message : "TLS error");
91}
92
93static ant_value_t js_tls_context_close(ant_t *js, ant_value_t *args, int nargs) {
94 ant_tls_context_wrap_t *wrap = tls_context_data(js_getthis(js));
95 if (!wrap) return js_getthis(js);
96
97 tls_context_free(wrap);
98 js_set_native_ptr(wrap->obj, NULL);
99 js_set_native_tag(wrap->obj, 0);
100 return js_getthis(js);
101}
102
103static void tls_init_context_proto(ant_t *js) {
104 if (g_tls_context_proto) return;
105
106 g_tls_context_proto = js_mkobj(js);
107 js_set(js, g_tls_context_proto, "close", js_mkfun(js_tls_context_close));
108}
109
110static ant_value_t js_tls_create_context(ant_t *js, ant_value_t *args, int nargs) {
111 ant_value_t options = nargs > 0 ? args[0] : js_mkundef();
112 ant_value_t obj = 0;
113 ant_value_t error = js_mkundef();
114
115 ant_tls_context_wrap_t *wrap = NULL;
116 ant_value_t ca_v = js_mkundef();
117 ant_value_t key_v = js_mkundef();
118 ant_value_t cert_v = js_mkundef();
119 ant_value_t partial_v = js_mkundef();
120
121 const char *ca = NULL;
122 const char *key_data = NULL;
123 const char *cert_data = NULL;
124
125 size_t ca_len = 0;
126 size_t key_len = 0;
127 size_t cert_len = 0;
128 int rc = 0;
129
130 if (vtype(options) != T_UNDEF && vtype(options) != T_NULL && vtype(options) != T_OBJ)
131 return js_mkerr_typed(js, JS_ERR_TYPE, "TLS context options must be an object");
132
133 tls_init_context_proto(js);
134
135 if (vtype(options) == T_OBJ) {
136 ca_v = js_get(js, options, "ca");
137 key_v = js_get(js, options, "key");
138 cert_v = js_get(js, options, "cert");
139 partial_v = js_get(js, options, "allowPartialChain");
140 }
141
142 if (!tls_value_bytes(js, ca_v, &ca, &ca_len, &error)) return error;
143 if (!tls_value_bytes(js, key_v, &key_data, &key_len, &error)) return error;
144 if (!tls_value_bytes(js, cert_v, &cert_data, &cert_len, &error)) return error;
145
146 wrap = calloc(1, sizeof(*wrap));
147 if (!wrap) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
148
149 wrap->ctx = default_tls_context(ca, ca_len);
150 if (!wrap->ctx) {
151 free(wrap);
152 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to create TLS context");
153 }
154
155 if (wrap->ctx->allow_partial_chain && js_truthy(js, partial_v)) {
156 rc = wrap->ctx->allow_partial_chain(wrap->ctx, 1);
157 if (rc != 0) {
158 ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to enable partial chain verification");
159 tls_context_free(wrap);
160 free(wrap);
161 return err;
162 }}
163
164 if (key_data) {
165 if (!wrap->ctx->load_key) {
166 tls_context_free(wrap);
167 free(wrap);
168 return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support loading private keys");
169 }
170
171 rc = wrap->ctx->load_key(&wrap->key, key_data, key_len);
172 if (rc != 0) {
173 ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to load TLS private key");
174 tls_context_free(wrap);
175 free(wrap);
176 return err;
177 }
178
179 if (cert_data) {
180 if (!wrap->ctx->load_cert) {
181 tls_context_free(wrap);
182 free(wrap);
183 return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support loading certificates");
184 }
185
186 rc = wrap->ctx->load_cert(&wrap->cert, cert_data, cert_len);
187 if (rc != 0) {
188 ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to load TLS certificate");
189 tls_context_free(wrap);
190 free(wrap);
191 return err;
192 }}
193
194 if (!wrap->ctx->set_own_cert) {
195 tls_context_free(wrap);
196 free(wrap);
197 return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support own certificate configuration");
198 }
199
200 rc = wrap->ctx->set_own_cert(wrap->ctx, wrap->key, wrap->cert);
201 if (rc != 0) {
202 ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to configure TLS certificate");
203 tls_context_free(wrap);
204 free(wrap);
205 return err;
206 }
207 } else if (cert_data) {
208 tls_context_free(wrap);
209 free(wrap);
210 return js_mkerr_typed(js, JS_ERR_TYPE, "TLS certificate requires a private key");
211 }
212
213 obj = js_mkobj(js);
214 js_set_proto_init(obj, g_tls_context_proto);
215 wrap->obj = obj;
216
217 js_set_native_ptr(obj, wrap);
218 js_set_native_tag(obj, TLS_CONTEXT_NATIVE_TAG);
219
220 return obj;
221}
222
223static ant_value_t js_tls_is_context(ant_t *js, ant_value_t *args, int nargs) {
224 ant_tls_context_wrap_t *wrap = nargs > 0 ? tls_context_data(args[0]) : NULL;
225 return js_bool(wrap && !wrap->closed && wrap->ctx);
226}
227
228static ant_value_t js_tls_set_config_path(ant_t *js, ant_value_t *args, int nargs) {
229 ant_value_t str_value = js_mkundef();
230 const char *path = NULL;
231 int rc = 0;
232
233 if (nargs < 1 || vtype(args[0]) == T_UNDEF || vtype(args[0]) == T_NULL) {
234 rc = tlsuv_set_config_path(NULL);
235 if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc));
236 return js_mkundef();
237 }
238
239 str_value = js_tostring_val(js, args[0]);
240 if (is_err(str_value)) return str_value;
241
242 path = js_getstr(js, str_value, NULL);
243 if (!path) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS config path");
244
245 rc = tlsuv_set_config_path(path);
246 if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc));
247
248 return js_mkundef();
249}
250
251ant_value_t internal_tls_library(ant_t *js) {
252 ant_value_t lib = js_mkobj(js);
253 ant_value_t version = js_mkstr(js, "unknown", 7);
254
255 tls_context *ctx = default_tls_context(NULL, 0);
256 const char *version_str = NULL;
257
258 if (ctx && ctx->version) {
259 version_str = ctx->version();
260 if (version_str) version = js_mkstr(js, version_str, strlen(version_str));
261 }
262
263 if (ctx && ctx->free_ctx) ctx->free_ctx(ctx);
264 tls_init_context_proto(js);
265
266 js_set(js, lib, "version", version);
267 js_set(js, lib, "createContext", js_mkfun(js_tls_create_context));
268 js_set(js, lib, "isContext", js_mkfun(js_tls_is_context));
269 js_set(js, lib, "setConfigPath", js_mkfun(js_tls_set_config_path));
270
271 return lib;
272}