@gjsify/tls-native 0.4.30 → 0.4.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/index.gjs.spec.js +1 -1
- package/lib/esm/index.js +1 -1
- package/lib/types/index.d.ts +95 -0
- package/meson.build +13 -1
- package/package.json +3 -3
- package/prebuilds/linux-aarch64/GjsifyTls-1.0.gir +122 -0
- package/prebuilds/linux-aarch64/GjsifyTls-1.0.typelib +0 -0
- package/prebuilds/linux-aarch64/libgjsifytls.so +0 -0
- package/prebuilds/linux-ppc64/GjsifyTls-1.0.gir +122 -0
- package/prebuilds/linux-ppc64/GjsifyTls-1.0.typelib +0 -0
- package/prebuilds/linux-ppc64/libgjsifytls.so +0 -0
- package/prebuilds/linux-riscv64/GjsifyTls-1.0.gir +122 -0
- package/prebuilds/linux-riscv64/GjsifyTls-1.0.typelib +0 -0
- package/prebuilds/linux-riscv64/libgjsifytls.so +0 -0
- package/prebuilds/linux-s390x/GjsifyTls-1.0.gir +122 -0
- package/prebuilds/linux-s390x/GjsifyTls-1.0.typelib +0 -0
- package/prebuilds/linux-s390x/libgjsifytls.so +0 -0
- package/prebuilds/linux-x86_64/GjsifyTls-1.0.gir +122 -0
- package/prebuilds/linux-x86_64/GjsifyTls-1.0.typelib +0 -0
- package/prebuilds/linux-x86_64/libgjsifytls.so +0 -0
- package/src/vala/gnutls-session.vapi +54 -0
- package/src/vala/session-access.vala +344 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SessionAccess — POC scaffold for Phase 2 (TLS session resumption +
|
|
3
|
+
* channel binding) of @gjsify/tls-native.
|
|
4
|
+
*
|
|
5
|
+
* Status
|
|
6
|
+
* ──────
|
|
7
|
+
* This class delivers the JS-visible *surface* (so `@gjsify/tls` can
|
|
8
|
+
* wire `getFinished()` / `getPeerFinished()` / `getSession()` /
|
|
9
|
+
* `setSession()` / `isSessionReused()` and the `'session'` event into
|
|
10
|
+
* `TLSSocket` with proper typings and consistent error semantics), but
|
|
11
|
+
* the actual GnuTLS interactions throw a clear "not supported" error
|
|
12
|
+
* pending the GIO-internal struct-layout work documented in
|
|
13
|
+
* `docs/poc/tls-phase2-session-access.md`.
|
|
14
|
+
*
|
|
15
|
+
* Why a POC and not a real impl: GnuTLS's session-resumption +
|
|
16
|
+
* channel-binding APIs all operate on a `gnutls_session_t` handle,
|
|
17
|
+
* which `Gio.TlsConnection` keeps private inside the glib-networking
|
|
18
|
+
* GnuTLS backend (`_GTlsConnectionGnutlsPrivate`). Reaching it requires
|
|
19
|
+
* either:
|
|
20
|
+
*
|
|
21
|
+
* (a) Walking the GTypeInstance private-data offset for
|
|
22
|
+
* `GTlsConnectionGnutls` — needs the exact struct layout from a
|
|
23
|
+
* known glib-networking commit (see the docs/poc note).
|
|
24
|
+
* (b) A future upstream patch to expose a `g_tls_connection_get_native_session()`
|
|
25
|
+
* (or similar) accessor.
|
|
26
|
+
*
|
|
27
|
+
* Until either path is available, every native call here returns the
|
|
28
|
+
* `GError` domain `GjsifyTls.session_access_quark()`/code
|
|
29
|
+
* `NOT_SUPPORTED` with a message pointing at the docs/poc note. The
|
|
30
|
+
* JS side surfaces this as a regular `Error` thrown synchronously
|
|
31
|
+
* from the `@gjsify/tls` getter — so consumers can detect it via
|
|
32
|
+
* `try/catch` AND gate up-front via `hasTlsSessionAccess()`.
|
|
33
|
+
*
|
|
34
|
+
* Wiring the real implementation later is intentionally a small
|
|
35
|
+
* change: each `throw_not_supported()` site below becomes a call into
|
|
36
|
+
* a new `_native_session_for_connection()` helper that returns the
|
|
37
|
+
* raw `gnutls_session_t`, then the existing GnuTLS APIs (already in
|
|
38
|
+
* Vala 0.56's `gnutls.vapi`, with `session_channel_binding` added in
|
|
39
|
+
* the sibling `gnutls-session.vapi`) take over. The JS-side bindings
|
|
40
|
+
* + tests don't need to change.
|
|
41
|
+
*
|
|
42
|
+
* Phase 2 scope
|
|
43
|
+
* ─────────────
|
|
44
|
+
* Three Node-equivalent capabilities, all blocked on the same
|
|
45
|
+
* `gnutls_session_t` extraction:
|
|
46
|
+
*
|
|
47
|
+
* 1. Session resumption — `getSession()`/`setSession()` data
|
|
48
|
+
* buffer for `gnutls_session_get_data2` / `gnutls_session_set_data`,
|
|
49
|
+
* `isSessionReused()` for `gnutls_session_is_resumed`, and a
|
|
50
|
+
* `'session'` event hook (`'new-session-ticket'` signal proxy).
|
|
51
|
+
* 2. Channel binding — `getFinished()` / `getPeerFinished()` are
|
|
52
|
+
* Node-compat aliases for the `tls-unique` channel-binding
|
|
53
|
+
* bytes (RFC 5929 §3, TLS 1.0–1.2). For TLS 1.3 the same APIs
|
|
54
|
+
* semantically degrade to `tls-exporter` (RFC 9266) — covered by
|
|
55
|
+
* the `get_channel_binding()` method taking a binding type.
|
|
56
|
+
* 3. Negotiated-protocol introspection beyond what Gio surfaces —
|
|
57
|
+
* not strictly Phase 2 but tracked here so the next iteration
|
|
58
|
+
* doesn't duplicate the bridge.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
using GLib;
|
|
62
|
+
|
|
63
|
+
namespace GjsifyTls {
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Error domain for {@link SessionAccess} failures.
|
|
67
|
+
*/
|
|
68
|
+
public errordomain SessionAccessError {
|
|
69
|
+
/**
|
|
70
|
+
* The underlying GIO/GnuTLS plumbing is not available — typically
|
|
71
|
+
* because the `gnutls_session_t` cannot be extracted from a
|
|
72
|
+
* `Gio.TlsConnection` in this GLib/glib-networking version.
|
|
73
|
+
* Track progress in `docs/poc/tls-phase2-session-access.md`.
|
|
74
|
+
*/
|
|
75
|
+
NOT_SUPPORTED,
|
|
76
|
+
/**
|
|
77
|
+
* The supplied `Gio.TlsConnection` is null or the handshake has
|
|
78
|
+
* not yet completed — the GnuTLS session is not ready.
|
|
79
|
+
*/
|
|
80
|
+
NOT_READY,
|
|
81
|
+
/**
|
|
82
|
+
* The GnuTLS call returned a non-zero error code. The {@link
|
|
83
|
+
* SessionAccessError.code} message includes the GnuTLS error
|
|
84
|
+
* string when available.
|
|
85
|
+
*/
|
|
86
|
+
GNUTLS_ERROR,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Symbolic channel-binding selector for {@link SessionAccess.get_channel_binding}.
|
|
91
|
+
*
|
|
92
|
+
* Mirrors GnuTLS's `gnutls_channel_binding_t` so the JS layer can
|
|
93
|
+
* pass int constants without depending on the GnuTLS GIR (GnuTLS
|
|
94
|
+
* has no GIR; values are stable per RFC 5929 / RFC 9266 / GnuTLS
|
|
95
|
+
* API stability).
|
|
96
|
+
*/
|
|
97
|
+
public enum ChannelBindingType {
|
|
98
|
+
/** `tls-unique` (RFC 5929 §3). TLS 1.0–1.2 only. */
|
|
99
|
+
TLS_UNIQUE = 0,
|
|
100
|
+
/** `tls-server-end-point` (RFC 5929 §4). */
|
|
101
|
+
TLS_SERVER_END_POINT = 1,
|
|
102
|
+
/** `tls-exporter` (RFC 9266). TLS 1.3 replacement for `tls-unique`. */
|
|
103
|
+
TLS_EXPORTER = 2,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* SessionAccess — wraps a `Gio.TlsConnection` to extract / inject
|
|
108
|
+
* data that the GIO API does not expose: serialized session
|
|
109
|
+
* resumption blobs, channel-binding bytes for SCRAM-SHA-* SASL,
|
|
110
|
+
* and the `is_resumed` predicate.
|
|
111
|
+
*
|
|
112
|
+
* Construction
|
|
113
|
+
* ────────────
|
|
114
|
+
* Created via {@link SessionAccess.for_connection} — the binding
|
|
115
|
+
* holds a strong ref on the connection so the session stays alive
|
|
116
|
+
* for the bridge's lifetime. The connection MUST have completed a
|
|
117
|
+
* handshake before any of the extraction APIs are called; calling
|
|
118
|
+
* earlier yields {@link SessionAccessError.NOT_READY}.
|
|
119
|
+
*
|
|
120
|
+
* Native session pointer
|
|
121
|
+
* ──────────────────────
|
|
122
|
+
* Every method below resolves the `gnutls_session_t` via
|
|
123
|
+
* {@link _resolve_native_session}, which currently always returns
|
|
124
|
+
* `null` and triggers a {@link SessionAccessError.NOT_SUPPORTED}.
|
|
125
|
+
* When the GIO struct-layout question is resolved, that single
|
|
126
|
+
* function becomes the only file that changes — the public
|
|
127
|
+
* surface stays stable.
|
|
128
|
+
*/
|
|
129
|
+
public class SessionAccess : GLib.Object {
|
|
130
|
+
|
|
131
|
+
/** Strong ref on the wrapped connection.
|
|
132
|
+
*
|
|
133
|
+
* Note: Vala maps GIO into the `GLib` namespace (see
|
|
134
|
+
* `gio-2.0.vapi` — `[CCode (gir_namespace = "Gio")]`
|
|
135
|
+
* `namespace GLib {`). So `Gio.TlsConnection` in JS / GIR
|
|
136
|
+
* corresponds to `GLib.TlsConnection` in Vala. The class
|
|
137
|
+
* surface published to JS via the GIR still appears as
|
|
138
|
+
* `Gio.TlsConnection` (the gir_namespace attribute drives
|
|
139
|
+
* the introspection output). */
|
|
140
|
+
private GLib.TlsConnection _connection;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Returns whether SessionAccess is functional in this runtime.
|
|
144
|
+
*
|
|
145
|
+
* Today this always returns `false` (Phase 2 POC). When the
|
|
146
|
+
* GIO-internal `gnutls_session_t` access lands, this returns
|
|
147
|
+
* `true` for connections whose backend is the GnuTLS one
|
|
148
|
+
* (`GTlsConnectionGnutls` instance) and `false` for any other
|
|
149
|
+
* (e.g. a hypothetical OpenSSL backend, or a mock).
|
|
150
|
+
*
|
|
151
|
+
* Consumers should call this BEFORE constructing a
|
|
152
|
+
* {@link SessionAccess} — passing the result through to
|
|
153
|
+
* `hasTlsSessionAccess()` on the JS side.
|
|
154
|
+
*/
|
|
155
|
+
public static bool is_supported () {
|
|
156
|
+
// POC: always false. See `_resolve_native_session()` below.
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Build a SessionAccess for a live `Gio.TlsConnection`. The
|
|
162
|
+
* connection is retained until this object is collected.
|
|
163
|
+
*
|
|
164
|
+
* Returns `null` if @connection is `null` — callers can use
|
|
165
|
+
* the null-coalescing pattern to short-circuit.
|
|
166
|
+
*/
|
|
167
|
+
public static SessionAccess? for_connection (GLib.TlsConnection? connection) {
|
|
168
|
+
if (connection == null) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return new SessionAccess.with_connection (connection);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Internal constructor — use {@link for_connection}. */
|
|
175
|
+
private SessionAccess.with_connection (GLib.TlsConnection connection) {
|
|
176
|
+
this._connection = connection;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Returns `true` if the underlying TLS session was resumed
|
|
181
|
+
* from a session ticket or session ID rather than completing
|
|
182
|
+
* a full handshake.
|
|
183
|
+
*
|
|
184
|
+
* Wraps `gnutls_session_is_resumed`.
|
|
185
|
+
*
|
|
186
|
+
* @throws SessionAccessError if the native session cannot be
|
|
187
|
+
* accessed (currently: always, until the Phase 2
|
|
188
|
+
* struct-layout work lands).
|
|
189
|
+
*/
|
|
190
|
+
public bool is_session_reused () throws SessionAccessError {
|
|
191
|
+
_resolve_native_session ();
|
|
192
|
+
// Unreachable today — _resolve_native_session() always throws.
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Extract the serialized session-resumption blob from the
|
|
198
|
+
* current handshake. Suitable for stashing in a JS variable
|
|
199
|
+
* and feeding back into a subsequent connect call via
|
|
200
|
+
* {@link set_session_data} (the `{session}` option on the
|
|
201
|
+
* Node-side `tls.connect()`).
|
|
202
|
+
*
|
|
203
|
+
* Wraps `gnutls_session_get_data2`.
|
|
204
|
+
*
|
|
205
|
+
* @returns serialized session as `Bytes` on success.
|
|
206
|
+
* @throws SessionAccessError if the native session cannot be
|
|
207
|
+
* accessed.
|
|
208
|
+
*/
|
|
209
|
+
public GLib.Bytes get_session_data () throws SessionAccessError {
|
|
210
|
+
_resolve_native_session ();
|
|
211
|
+
return new GLib.Bytes (new uint8[0]);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Inject a previously serialized session blob to attempt
|
|
216
|
+
* resumption. Must be called BEFORE the handshake completes
|
|
217
|
+
* — typically right after `Gio.TlsClientConnection.new()` and
|
|
218
|
+
* before `handshake_async()`.
|
|
219
|
+
*
|
|
220
|
+
* Wraps `gnutls_session_set_data`.
|
|
221
|
+
*
|
|
222
|
+
* @param data serialized blob from a prior {@link get_session_data} call.
|
|
223
|
+
* @throws SessionAccessError if the native session cannot be
|
|
224
|
+
* accessed.
|
|
225
|
+
*/
|
|
226
|
+
public void set_session_data (GLib.Bytes data) throws SessionAccessError {
|
|
227
|
+
_resolve_native_session ();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Extract the TLS-Finished bytes for the given channel-binding
|
|
232
|
+
* type. The default (`TLS_UNIQUE`) matches Node's
|
|
233
|
+
* `tlsSocket.getFinished()` semantics; `TLS_EXPORTER` is the
|
|
234
|
+
* TLS 1.3 replacement.
|
|
235
|
+
*
|
|
236
|
+
* Wraps `gnutls_session_channel_binding`.
|
|
237
|
+
*
|
|
238
|
+
* @param binding the binding type — see {@link ChannelBindingType}.
|
|
239
|
+
* @returns the binding bytes on success.
|
|
240
|
+
* @throws SessionAccessError if the native session cannot be
|
|
241
|
+
* accessed or the binding type is not supported by
|
|
242
|
+
* the negotiated TLS version.
|
|
243
|
+
*/
|
|
244
|
+
public GLib.Bytes get_channel_binding (ChannelBindingType binding = ChannelBindingType.TLS_UNIQUE)
|
|
245
|
+
throws SessionAccessError {
|
|
246
|
+
_resolve_native_session ();
|
|
247
|
+
return new GLib.Bytes (new uint8[0]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Convenience: `getFinished()` per Node's TLSSocket API.
|
|
252
|
+
* Returns the local Finished bytes (i.e. the bytes WE sent).
|
|
253
|
+
* On TLS 1.3 returns the `tls-exporter` material instead.
|
|
254
|
+
*
|
|
255
|
+
* Same blocker as {@link get_channel_binding}.
|
|
256
|
+
*/
|
|
257
|
+
public GLib.Bytes get_finished () throws SessionAccessError {
|
|
258
|
+
// TLS 1.3 vs 1.2 selection happens here once
|
|
259
|
+
// _resolve_native_session() returns a real handle — for
|
|
260
|
+
// 1.3 we'd call get_channel_binding(TLS_EXPORTER) and
|
|
261
|
+
// for ≤1.2 get_channel_binding(TLS_UNIQUE). Today the
|
|
262
|
+
// throw is unconditional.
|
|
263
|
+
return get_channel_binding (ChannelBindingType.TLS_UNIQUE);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Convenience: `getPeerFinished()` per Node's TLSSocket API.
|
|
268
|
+
* Returns the peer's Finished bytes. Same TLS 1.3 fallback as
|
|
269
|
+
* {@link get_finished}.
|
|
270
|
+
*
|
|
271
|
+
* Implementation note: GnuTLS's `gnutls_session_channel_binding`
|
|
272
|
+
* with `TLS_UNIQUE` returns the local-side Finished. For the
|
|
273
|
+
* peer-side bytes we need a sibling call that reads the
|
|
274
|
+
* remote Finished from the same session state — exists as
|
|
275
|
+
* `gnutls_session_get_random_*` + manual derivation, OR via
|
|
276
|
+
* the same TLS_UNIQUE binding bytes which by design are
|
|
277
|
+
* symmetric for both peers on the same session (the binding
|
|
278
|
+
* is shared, not directional). Node distinguishes
|
|
279
|
+
* `getFinished()` vs `getPeerFinished()` because OpenSSL
|
|
280
|
+
* exposes both halves separately; for SCRAM-SHA-* the value
|
|
281
|
+
* actually used is `tls-unique` (shared) so the distinction
|
|
282
|
+
* is informational. See docs/poc note for the precise
|
|
283
|
+
* mapping the Path-A implementation will use.
|
|
284
|
+
*/
|
|
285
|
+
public GLib.Bytes get_peer_finished () throws SessionAccessError {
|
|
286
|
+
return get_channel_binding (ChannelBindingType.TLS_UNIQUE);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get the protocol version actually negotiated, as a stable
|
|
291
|
+
* string. Mirrors `Gio.TlsConnection.get_protocol_version()`
|
|
292
|
+
* but is colocated here so the JS-side `SessionAccess`
|
|
293
|
+
* wrapper has one consistent surface — useful when the
|
|
294
|
+
* `tls-unique` vs `tls-exporter` switch in
|
|
295
|
+
* {@link get_finished} needs the live version.
|
|
296
|
+
*
|
|
297
|
+
* Unlike the rest of this class, this method DOES return a
|
|
298
|
+
* useful value today — it reads `Gio.TlsConnection.get_protocol_version()`
|
|
299
|
+
* directly. POC value: lets tests / consumers exercise the
|
|
300
|
+
* SessionAccess shape end-to-end even before the gnutls
|
|
301
|
+
* blocker is resolved.
|
|
302
|
+
*/
|
|
303
|
+
public string get_negotiated_protocol_version () {
|
|
304
|
+
var proto = _connection.get_protocol_version ();
|
|
305
|
+
switch (proto) {
|
|
306
|
+
case GLib.TlsProtocolVersion.TLS_1_0: return "TLSv1";
|
|
307
|
+
case GLib.TlsProtocolVersion.TLS_1_1: return "TLSv1.1";
|
|
308
|
+
case GLib.TlsProtocolVersion.TLS_1_2: return "TLSv1.2";
|
|
309
|
+
case GLib.TlsProtocolVersion.TLS_1_3: return "TLSv1.3";
|
|
310
|
+
default: return "unknown";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Resolve the `gnutls_session_t` for {@link _connection}.
|
|
316
|
+
*
|
|
317
|
+
* Today this always throws {@link SessionAccessError.NOT_SUPPORTED}.
|
|
318
|
+
* The eventual implementation needs to:
|
|
319
|
+
*
|
|
320
|
+
* 1. Confirm `_connection` is a `GTlsConnectionGnutls`
|
|
321
|
+
* instance (`g_type_check_instance_is_a` against the
|
|
322
|
+
* type from `gio-tls-gnutls.so`).
|
|
323
|
+
* 2. Read the `session` field from
|
|
324
|
+
* `((GTlsConnectionGnutls*)_connection)->priv->session`
|
|
325
|
+
* — the layout comes from `glib-networking/tls/gnutls/
|
|
326
|
+
* gtlsconnection-gnutls-base.c`.
|
|
327
|
+
* 3. Return the pointer as `void*` so the GnuTLS calls in
|
|
328
|
+
* the methods above can use it.
|
|
329
|
+
*
|
|
330
|
+
* Until that lands, the unconditional throw keeps the API
|
|
331
|
+
* surface honest. See docs/poc/tls-phase2-session-access.md
|
|
332
|
+
* for the open questions and acceptance criteria for Path A.
|
|
333
|
+
*/
|
|
334
|
+
private void* _resolve_native_session () throws SessionAccessError {
|
|
335
|
+
throw new SessionAccessError.NOT_SUPPORTED (
|
|
336
|
+
"@gjsify/tls-native SessionAccess: extracting gnutls_session_t " +
|
|
337
|
+
"from Gio.TlsConnection is not yet implemented. The Phase 2 " +
|
|
338
|
+
"native bits ship as a POC scaffold; see " +
|
|
339
|
+
"docs/poc/tls-phase2-session-access.md for the open struct-layout " +
|
|
340
|
+
"question that gates the real implementation."
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|