@gjsify/tls-native 0.4.30 → 0.4.32
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 +26 -1
- package/package.json +9 -4
- 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/gjsify-tls-private.vapi +39 -0
- package/src/vala/gnutls-session.vapi +54 -0
- package/src/vala/session-access.vala +335 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SessionAccess — Phase 2 (TLS session resumption + channel binding)
|
|
3
|
+
* of @gjsify/tls-native.
|
|
4
|
+
*
|
|
5
|
+
* Status
|
|
6
|
+
* ──────
|
|
7
|
+
* Functional Path-A implementation. Every method below delegates to a
|
|
8
|
+
* thin C shim (`src/c/gjsify-tls-private.{c,h}`) that reaches into
|
|
9
|
+
* glib-networking's GnuTLS-backend private struct via the GLib 2.38+
|
|
10
|
+
* public `g_type_instance_get_private` + a runtime
|
|
11
|
+
* `g_type_from_name("GTlsConnectionGnutls")` lookup. The struct layout
|
|
12
|
+
* itself is vendored from `refs/glib-networking/tls/gnutls/
|
|
13
|
+
* gtlsconnection-gnutls.c` (see the file-header in the C shim for the
|
|
14
|
+
* vendored offsets + supported window).
|
|
15
|
+
*
|
|
16
|
+
* Backwards-compatibility on a non-GnuTLS backend
|
|
17
|
+
* ───────────────────────────────────────────────
|
|
18
|
+
* If `g_type_from_name("GTlsConnectionGnutls")` returns 0 (i.e. a
|
|
19
|
+
* hypothetical future OpenSSL backend is selected via `GIO_USE_TLS`),
|
|
20
|
+
* the static `is_supported()` returns `false` and every consumer call
|
|
21
|
+
* surfaces `SessionAccessError.NOT_SUPPORTED` — gracefully degrading
|
|
22
|
+
* the same way Node's TLSSocket does when built without session
|
|
23
|
+
* support. The JS-side `hasTlsSessionAccess()` predicate is the
|
|
24
|
+
* canonical gate consumers should check; see the per-method docs for
|
|
25
|
+
* the corresponding error semantics.
|
|
26
|
+
*
|
|
27
|
+
* Phase 2 scope
|
|
28
|
+
* ─────────────
|
|
29
|
+
* Three Node-equivalent capabilities, all blocked on the same
|
|
30
|
+
* `gnutls_session_t` extraction:
|
|
31
|
+
*
|
|
32
|
+
* 1. Session resumption — `getSession()`/`setSession()` data
|
|
33
|
+
* buffer for `gnutls_session_get_data2` / `gnutls_session_set_data`,
|
|
34
|
+
* `isSessionReused()` for `gnutls_session_is_resumed`, and a
|
|
35
|
+
* `'session'` event hook (`'new-session-ticket'` signal proxy).
|
|
36
|
+
* 2. Channel binding — `getFinished()` / `getPeerFinished()` are
|
|
37
|
+
* Node-compat aliases for the `tls-unique` channel-binding
|
|
38
|
+
* bytes (RFC 5929 §3, TLS 1.0–1.2). For TLS 1.3 the same APIs
|
|
39
|
+
* semantically degrade to `tls-exporter` (RFC 9266) — covered by
|
|
40
|
+
* the `get_channel_binding()` method taking a binding type.
|
|
41
|
+
* 3. Negotiated-protocol introspection beyond what Gio surfaces —
|
|
42
|
+
* not strictly Phase 2 but tracked here so the next iteration
|
|
43
|
+
* doesn't duplicate the bridge.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
using GLib;
|
|
47
|
+
|
|
48
|
+
namespace GjsifyTls {
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Error domain for {@link SessionAccess} failures.
|
|
52
|
+
*/
|
|
53
|
+
public errordomain SessionAccessError {
|
|
54
|
+
/**
|
|
55
|
+
* The underlying GIO/GnuTLS plumbing is not available — typically
|
|
56
|
+
* because the `gnutls_session_t` cannot be extracted from a
|
|
57
|
+
* `Gio.TlsConnection` in this GLib/glib-networking version.
|
|
58
|
+
* Track progress in `docs/poc/tls-phase2-session-access.md`.
|
|
59
|
+
*/
|
|
60
|
+
NOT_SUPPORTED,
|
|
61
|
+
/**
|
|
62
|
+
* The supplied `Gio.TlsConnection` is null or the handshake has
|
|
63
|
+
* not yet completed — the GnuTLS session is not ready.
|
|
64
|
+
*/
|
|
65
|
+
NOT_READY,
|
|
66
|
+
/**
|
|
67
|
+
* The GnuTLS call returned a non-zero error code. The {@link
|
|
68
|
+
* SessionAccessError.code} message includes the GnuTLS error
|
|
69
|
+
* string when available.
|
|
70
|
+
*/
|
|
71
|
+
GNUTLS_ERROR,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Symbolic channel-binding selector for {@link SessionAccess.get_channel_binding}.
|
|
76
|
+
*
|
|
77
|
+
* Mirrors GnuTLS's `gnutls_channel_binding_t` so the JS layer can
|
|
78
|
+
* pass int constants without depending on the GnuTLS GIR (GnuTLS
|
|
79
|
+
* has no GIR; values are stable per RFC 5929 / RFC 9266 / GnuTLS
|
|
80
|
+
* API stability).
|
|
81
|
+
*/
|
|
82
|
+
public enum ChannelBindingType {
|
|
83
|
+
/** `tls-unique` (RFC 5929 §3). TLS 1.0–1.2 only. */
|
|
84
|
+
TLS_UNIQUE = 0,
|
|
85
|
+
/** `tls-server-end-point` (RFC 5929 §4). */
|
|
86
|
+
TLS_SERVER_END_POINT = 1,
|
|
87
|
+
/** `tls-exporter` (RFC 9266). TLS 1.3 replacement for `tls-unique`. */
|
|
88
|
+
TLS_EXPORTER = 2,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* SessionAccess — wraps a `Gio.TlsConnection` to extract / inject
|
|
93
|
+
* data that the GIO API does not expose: serialized session
|
|
94
|
+
* resumption blobs, channel-binding bytes for SCRAM-SHA-* SASL,
|
|
95
|
+
* and the `is_resumed` predicate.
|
|
96
|
+
*
|
|
97
|
+
* Construction
|
|
98
|
+
* ────────────
|
|
99
|
+
* Created via {@link SessionAccess.for_connection} — the binding
|
|
100
|
+
* holds a strong ref on the connection so the session stays alive
|
|
101
|
+
* for the bridge's lifetime. The connection MUST have completed a
|
|
102
|
+
* handshake before any of the extraction APIs are called; calling
|
|
103
|
+
* earlier yields {@link SessionAccessError.NOT_READY}.
|
|
104
|
+
*
|
|
105
|
+
* Native session pointer
|
|
106
|
+
* ──────────────────────
|
|
107
|
+
* Every method below resolves the `gnutls_session_t` via
|
|
108
|
+
* {@link _resolve_native_session}, which currently always returns
|
|
109
|
+
* `null` and triggers a {@link SessionAccessError.NOT_SUPPORTED}.
|
|
110
|
+
* When the GIO struct-layout question is resolved, that single
|
|
111
|
+
* function becomes the only file that changes — the public
|
|
112
|
+
* surface stays stable.
|
|
113
|
+
*/
|
|
114
|
+
public class SessionAccess : GLib.Object {
|
|
115
|
+
|
|
116
|
+
/** Strong ref on the wrapped connection.
|
|
117
|
+
*
|
|
118
|
+
* Note: Vala maps GIO into the `GLib` namespace (see
|
|
119
|
+
* `gio-2.0.vapi` — `[CCode (gir_namespace = "Gio")]`
|
|
120
|
+
* `namespace GLib {`). So `Gio.TlsConnection` in JS / GIR
|
|
121
|
+
* corresponds to `GLib.TlsConnection` in Vala. The class
|
|
122
|
+
* surface published to JS via the GIR still appears as
|
|
123
|
+
* `Gio.TlsConnection` (the gir_namespace attribute drives
|
|
124
|
+
* the introspection output). */
|
|
125
|
+
private GLib.TlsConnection _connection;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Returns whether SessionAccess is functional in this runtime.
|
|
129
|
+
*
|
|
130
|
+
* Returns `true` when glib-networking's GnuTLS backend is the
|
|
131
|
+
* active TLS backend (`GTlsConnectionGnutls` GType is
|
|
132
|
+
* registered). Returns `false` only when a non-GnuTLS GIO TLS
|
|
133
|
+
* backend is selected (e.g. via `GIO_USE_TLS=openssl` once
|
|
134
|
+
* that backend exists upstream).
|
|
135
|
+
*
|
|
136
|
+
* Consumers should call this BEFORE constructing a
|
|
137
|
+
* {@link SessionAccess} — passing the result through to
|
|
138
|
+
* `hasTlsSessionAccess()` on the JS side.
|
|
139
|
+
*/
|
|
140
|
+
public static bool is_supported () {
|
|
141
|
+
return GjsifyTlsPrivate.is_supported ();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Build a SessionAccess for a live `Gio.TlsConnection`. The
|
|
146
|
+
* connection is retained until this object is collected.
|
|
147
|
+
*
|
|
148
|
+
* Returns `null` if @connection is `null` — callers can use
|
|
149
|
+
* the null-coalescing pattern to short-circuit.
|
|
150
|
+
*/
|
|
151
|
+
public static SessionAccess? for_connection (GLib.TlsConnection? connection) {
|
|
152
|
+
if (connection == null) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return new SessionAccess.with_connection (connection);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Internal constructor — use {@link for_connection}. */
|
|
159
|
+
private SessionAccess.with_connection (GLib.TlsConnection connection) {
|
|
160
|
+
this._connection = connection;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Returns `true` if the underlying TLS session was resumed
|
|
165
|
+
* from a session ticket or session ID rather than completing
|
|
166
|
+
* a full handshake.
|
|
167
|
+
*
|
|
168
|
+
* Wraps `gnutls_session_is_resumed`.
|
|
169
|
+
*
|
|
170
|
+
* @throws SessionAccessError if the connection is not from
|
|
171
|
+
* the GnuTLS backend (`NOT_SUPPORTED`) or the GnuTLS
|
|
172
|
+
* API itself failed (`GNUTLS_ERROR`).
|
|
173
|
+
*/
|
|
174
|
+
public bool is_session_reused () throws SessionAccessError {
|
|
175
|
+
try {
|
|
176
|
+
return GjsifyTlsPrivate.is_session_reused (this._connection);
|
|
177
|
+
} catch (GjsifyTlsPrivate.Error e) {
|
|
178
|
+
throw _wrap (e);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Extract the serialized session-resumption blob from the
|
|
184
|
+
* current handshake. Suitable for stashing in a JS variable
|
|
185
|
+
* and feeding back into a subsequent connect call via
|
|
186
|
+
* {@link set_session_data} (the `{session}` option on the
|
|
187
|
+
* Node-side `tls.connect()`).
|
|
188
|
+
*
|
|
189
|
+
* Wraps `gnutls_session_get_data2`.
|
|
190
|
+
*
|
|
191
|
+
* @returns serialized session as `Bytes` on success.
|
|
192
|
+
* @throws SessionAccessError if the native session cannot be
|
|
193
|
+
* accessed.
|
|
194
|
+
*/
|
|
195
|
+
public GLib.Bytes get_session_data () throws SessionAccessError {
|
|
196
|
+
try {
|
|
197
|
+
return GjsifyTlsPrivate.get_session_data (this._connection);
|
|
198
|
+
} catch (GjsifyTlsPrivate.Error e) {
|
|
199
|
+
throw _wrap (e);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Inject a previously serialized session blob to attempt
|
|
205
|
+
* resumption. Must be called BEFORE the handshake completes
|
|
206
|
+
* — typically right after `Gio.TlsClientConnection.new()` and
|
|
207
|
+
* before `handshake_async()`.
|
|
208
|
+
*
|
|
209
|
+
* Wraps `gnutls_session_set_data`.
|
|
210
|
+
*
|
|
211
|
+
* @param data serialized blob from a prior {@link get_session_data} call.
|
|
212
|
+
* @throws SessionAccessError if the native session cannot be
|
|
213
|
+
* accessed.
|
|
214
|
+
*/
|
|
215
|
+
public void set_session_data (GLib.Bytes data) throws SessionAccessError {
|
|
216
|
+
try {
|
|
217
|
+
GjsifyTlsPrivate.set_session_data (this._connection, data);
|
|
218
|
+
} catch (GjsifyTlsPrivate.Error e) {
|
|
219
|
+
throw _wrap (e);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Extract the TLS-Finished bytes for the given channel-binding
|
|
225
|
+
* type. The default (`TLS_UNIQUE`) matches Node's
|
|
226
|
+
* `tlsSocket.getFinished()` semantics; `TLS_EXPORTER` is the
|
|
227
|
+
* TLS 1.3 replacement.
|
|
228
|
+
*
|
|
229
|
+
* Wraps `gnutls_session_channel_binding`.
|
|
230
|
+
*
|
|
231
|
+
* @param binding the binding type — see {@link ChannelBindingType}.
|
|
232
|
+
* @returns the binding bytes on success.
|
|
233
|
+
* @throws SessionAccessError if the native session cannot be
|
|
234
|
+
* accessed or the binding type is not supported by
|
|
235
|
+
* the negotiated TLS version.
|
|
236
|
+
*/
|
|
237
|
+
public GLib.Bytes get_channel_binding (ChannelBindingType binding = ChannelBindingType.TLS_UNIQUE)
|
|
238
|
+
throws SessionAccessError {
|
|
239
|
+
try {
|
|
240
|
+
return GjsifyTlsPrivate.get_channel_binding (this._connection, (int) binding);
|
|
241
|
+
} catch (GjsifyTlsPrivate.Error e) {
|
|
242
|
+
throw _wrap (e);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Convenience: `getFinished()` per Node's TLSSocket API.
|
|
248
|
+
* Returns the local Finished bytes (i.e. the bytes WE sent).
|
|
249
|
+
* On TLS 1.3 returns the `tls-exporter` material instead.
|
|
250
|
+
*
|
|
251
|
+
* Same blocker as {@link get_channel_binding}.
|
|
252
|
+
*/
|
|
253
|
+
public GLib.Bytes get_finished () throws SessionAccessError {
|
|
254
|
+
// On TLS ≤1.2 the relevant binding is `tls-unique` (the
|
|
255
|
+
// first Finished message, RFC 5929 §3). On TLS 1.3 the
|
|
256
|
+
// Finished messages are encrypted before the channel-
|
|
257
|
+
// binding is taken, so RFC 9266 specifies `tls-exporter`
|
|
258
|
+
// as the replacement.
|
|
259
|
+
var binding = _connection.get_protocol_version () == GLib.TlsProtocolVersion.TLS_1_3
|
|
260
|
+
? ChannelBindingType.TLS_EXPORTER
|
|
261
|
+
: ChannelBindingType.TLS_UNIQUE;
|
|
262
|
+
return get_channel_binding (binding);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Convenience: `getPeerFinished()` per Node's TLSSocket API.
|
|
267
|
+
* Returns the peer's Finished bytes. Same TLS 1.3 fallback as
|
|
268
|
+
* {@link get_finished}.
|
|
269
|
+
*
|
|
270
|
+
* Implementation note: GnuTLS's `gnutls_session_channel_binding`
|
|
271
|
+
* with `TLS_UNIQUE` returns the local-side Finished. For the
|
|
272
|
+
* peer-side bytes we need a sibling call that reads the
|
|
273
|
+
* remote Finished from the same session state — exists as
|
|
274
|
+
* `gnutls_session_get_random_*` + manual derivation, OR via
|
|
275
|
+
* the same TLS_UNIQUE binding bytes which by design are
|
|
276
|
+
* symmetric for both peers on the same session (the binding
|
|
277
|
+
* is shared, not directional). Node distinguishes
|
|
278
|
+
* `getFinished()` vs `getPeerFinished()` because OpenSSL
|
|
279
|
+
* exposes both halves separately; for SCRAM-SHA-* the value
|
|
280
|
+
* actually used is `tls-unique` (shared) so the distinction
|
|
281
|
+
* is informational. See docs/poc note for the precise
|
|
282
|
+
* mapping the Path-A implementation will use.
|
|
283
|
+
*/
|
|
284
|
+
public GLib.Bytes get_peer_finished () throws SessionAccessError {
|
|
285
|
+
// Per the GnuTLS manual + RFC 5929/9266 the channel-binding
|
|
286
|
+
// bytes are symmetric across both peers — there is no
|
|
287
|
+
// separate "peer" Finished available via the GnuTLS API
|
|
288
|
+
// (OpenSSL exposes both halves; GnuTLS does not). For the
|
|
289
|
+
// SCRAM-SHA-* use case the symmetric binding IS what SASL
|
|
290
|
+
// negotiates against, so this is functionally correct.
|
|
291
|
+
// Same TLS-1.3 fallback as `get_finished()`.
|
|
292
|
+
return get_finished ();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get the protocol version actually negotiated, as a stable
|
|
297
|
+
* string. Mirrors `Gio.TlsConnection.get_protocol_version()`
|
|
298
|
+
* but is colocated here so the JS-side `SessionAccess`
|
|
299
|
+
* wrapper has one consistent surface — useful when the
|
|
300
|
+
* `tls-unique` vs `tls-exporter` switch in
|
|
301
|
+
* {@link get_finished} needs the live version.
|
|
302
|
+
*
|
|
303
|
+
* Unlike the rest of this class, this method DOES return a
|
|
304
|
+
* useful value today — it reads `Gio.TlsConnection.get_protocol_version()`
|
|
305
|
+
* directly. POC value: lets tests / consumers exercise the
|
|
306
|
+
* SessionAccess shape end-to-end even before the gnutls
|
|
307
|
+
* blocker is resolved.
|
|
308
|
+
*/
|
|
309
|
+
public string get_negotiated_protocol_version () {
|
|
310
|
+
var proto = _connection.get_protocol_version ();
|
|
311
|
+
switch (proto) {
|
|
312
|
+
case GLib.TlsProtocolVersion.TLS_1_0: return "TLSv1";
|
|
313
|
+
case GLib.TlsProtocolVersion.TLS_1_1: return "TLSv1.1";
|
|
314
|
+
case GLib.TlsProtocolVersion.TLS_1_2: return "TLSv1.2";
|
|
315
|
+
case GLib.TlsProtocolVersion.TLS_1_3: return "TLSv1.3";
|
|
316
|
+
default: return "unknown";
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Translate a {@link GjsifyTlsPrivate.Error} from the C shim
|
|
322
|
+
* into the public {@link SessionAccessError} domain, preserving
|
|
323
|
+
* the original message verbatim. The mapping is one-to-one:
|
|
324
|
+
* `NOT_SUPPORTED` → `NOT_SUPPORTED`, `GNUTLS_FAILED` →
|
|
325
|
+
* `GNUTLS_ERROR`. The `NOT_READY` code stays reserved for the
|
|
326
|
+
* Vala-side `null`-connection guard in {@link for_connection}.
|
|
327
|
+
*/
|
|
328
|
+
private SessionAccessError _wrap (GjsifyTlsPrivate.Error e) {
|
|
329
|
+
if (e is GjsifyTlsPrivate.Error.GNUTLS_FAILED) {
|
|
330
|
+
return new SessionAccessError.GNUTLS_ERROR (e.message);
|
|
331
|
+
}
|
|
332
|
+
return new SessionAccessError.NOT_SUPPORTED (e.message);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|