forked from WebAssembly/wasi-sockets
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtls.wit
484 lines (418 loc) · 21.7 KB
/
tls.wit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
interface tls {
use wasi:io/streams@0.2.0.{input-stream, output-stream};
use wasi:io/poll@0.2.0.{pollable};
/// TLS protocol version.
///
/// At the time of writing, these are the existing TLS versions:
/// - 0x0200: SSLv2 (Deprecated)
/// - 0x0300: SSLv3 (Deprecated)
/// - 0x0301: TLSv1.0 (Deprecated)
/// - 0x0302: TLSv1.1 (Deprecated)
/// - 0x0303: TLSv1.2
/// - 0x0304: TLSv1.3
///
/// TODO: Want to use regular WIT `enum` type, but then adding a new protocol is backwards incompatible.
type protocol-version = u16;
/// TLS Cipher suite.
///
/// These are maintained by IANA. Examples:
/// - 0x1301: TLS_AES_128_GCM_SHA256
/// - 0x1302: TLS_AES_256_GCM_SHA384
/// - 0xC030: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
/// - 0xC02B: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
/// - etc.
///
/// See: <https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4>
type cipher-suite = u16;
/// Application-Layer Protocol Negotiation (ALPN) protocol ID.
///
/// ALPN IDs are between 1 and 255 bytes long. Typically, they represent the
/// binary encoding of an ASCII string (e.g. `[0x68 0x32]` for `"h2"`),
/// though this is not required.
type alpn-id = list<u8>;
/// A X509 certificate chain; starting with the end-entity's certificate
/// followed by 0 or more intermediate certificates.
resource public-identity {
export-X509-chain: func() -> list<list<u8>>;
}
/// The combination of a private key with its public certificate(s).
/// The private key data can not be exported.
resource private-identity {
/// TODO: find a way to "preopen" these private-identity resources, so that the sensitive private key data never has to flow through the guest.
/// TODO: specify which exact binary format
parse: static func(private-key: list<u8>, x509-chain: list<list<u8>>) -> result<private-identity>;
public-identity: func() -> public-identity;
}
/// A TLS client connection.
///
/// The various properties on this type become available as the handshake
/// progresses.
///
/// # Design
/// TLS connections can be divided up into roughly two phases: the handshake
/// phase and the bulk data transfer phase. (TLS renegotiation is not exposed
/// in this interface.)
/// By far, most of the API complexity is in the handshake phase, which has
/// been split off into a separate `client-handshake` type. The
/// `client-handshake` is used to configure and control advanced (but optional)
/// operations such as custom server certificate validation
/// and client certificate selection. Once the `client-handshake` has been
/// set up appropriately, the handshake can be completed using the `finish`
/// method. That consumes the `client-handshake` and gives a pair of
/// cleartext streams back in return.
///
/// The `client-connection` and its handshake resource don't not perform
/// any I/O on their own. It is a pure stream "wrapper" that consumes raw
/// TLS data streams and exposes cleartext streams.
/// The `input` and `output` parameters of constructor map to the
/// raw TLS data streams and are typically obtained from a WASI TCP socket.
/// The `client-handshake::finish` method returns the cleartext streams.
///
/// ## Consumer drives the handshake
/// Many TLS libraries let users inject custom behavior at various points
/// of interest through the registration of callbacks.
/// The WebAssembly Component Model does not support callbacks. Instead, the
/// WASI TLS interface exposes direct-style APIs for the consumer to actively
/// drive the handshake forwards.
///
/// The `client-handshake` starts out "suspended". The consumer then drives
/// the handshake to the next point of interest. For example using the
/// `verify-server-identity` method. Calling it:
/// - resumes the handshake,
/// - asynchronously waits for the server to send its certificate
/// - upon receipt suspends the handshake again
/// - resolves the returned future.
/// It is then back up to the consumer to drive the handshake along to the
/// next point of interest (e.g. `receive-client-identity-request`)
/// or finish the handshake entirely (`finish`).
///
/// No data flows through the I/O streams while a client-handshake is suspended.
///
/// # Usage
/// The general usage pattern looks like this:
/// - Construct a new connection using the `client-connection` constructor.
/// - Obtain the handshake resource using `client-connection::connect`. The
/// handshake starts out suspended.
/// - (Optional) Further refine the settings using the various
/// `configure-*` methods. These may only be called while the
/// handshake is suspended.
/// - (Optional) Resume the handshake and wait for the server to send its
/// certificate using: `verify-server-identity`.
/// - (Optional) Resume the handshake and wait for the server to send a
/// client certificate request using: `receive-client-identity-request`.
/// - Resume and run the handshake to completion using: `finish`.
/// - Read & write application data into the streams returned by `finish`.
///
/// The optional steps must happen in the order as defined above.
///
/// ## Example
/// A minimal, but production-ready, example in pseudocode:
/// ```text
/// let conn = new client-connection(tcp-input, tcp-output);
/// let (tls-input, tls-output) = conn.connect("example.com").finish().await?;
/// ```
///
/// This assumes `tcp-input` and `tcp-output` are input/output streams
/// obtained from e.g. `tcp-socket::connect`.
///
/// # Secure by default
/// Implementations should pick reasonably safe defaults for all security
/// related settings. Users of this interface should be able to confidently
/// instantiate a new client connection and then, without further configuration,
/// immediately initiate the handshake. As demonstrated in the example above.
resource client-connection {
// TODO: handle post-handshake client certificate requests.
// TODO: graceful shutdown
/// Create a new connection instance that wraps the provided
/// raw TLS data streams.
constructor(input: input-stream, output: output-stream);
/// Create a new client handshake. This method may be called at most once.
/// This method itself does not perform any I/O. Use the returned resource
/// to actually drive the handshake forwards.
///
/// The returned `client-handshake` is a child resources of the `client-connection`.
connect: func(server-name: string) -> result<client-handshake>;
/// The server name that was provided to `connect`.
server-name: func() -> option<string>;
/// The negotiated ALPN ID, if any.
///
/// Returns `none` when:
/// - the client did not advertise any ALPN IDs, or:
/// - there was no intersection between the IDs advertised by the client
/// and the IDs supported by the server.
alpn-id: func() -> option<alpn-id>;
/// The client's identity advertised to the server, if any.
/// This will be one of the identities passed to
/// `client-identity-request::respond`.
///
/// Returns `none` when:
/// - the server did not request a client certificate,
/// - the server did request a client certificate, but there was no match
/// with the configured identities.
client-identity: func() -> option<private-identity>;
/// The verified certificate of the server.
server-identity: func() -> option<public-identity>;
/// The negotiated TLS protocol version.
protocol-version: func() -> option<protocol-version>;
/// The negotiated cipher suite.
cipher-suite: func() -> option<cipher-suite>;
}
/// Resource to control a TLS client handshake.
///
/// See `client-connection` for more information.
resource client-handshake {
/// Configure the ALPN IDs for the client to adertise to the server,
/// in descending order of preference.
///
/// By default, no ALPN IDs will be advertised.
configure-alpn-ids: func(value: list<alpn-id>) -> result;
// TODO: configure-protocol-versions: func(value: list<protocol-version>) -> result;
// TODO: configure-cipher-suites: func(value: list<cipher-suite>) -> result;
/// (Optional) Partially continue the handshake and verify the
/// certificate sent by the server.
///
/// Can be called at most once per handshake. Also, this method fails if
/// the handshake has already progressed too far for this to be possible.
///
/// The returned future is a child resources of the `client-handshake`.
///
/// TODO: See `server-handshake::request-client-identity` for TODOs.
verify-server-identity: func() -> result<future-public-identity>;
/// (Optional) Partially continue the handshake and wait for the server
/// to send a client certificate request. The future resolves with:
/// - `some(client-identity-request)` if the server indeed requested a
/// client certificate, or:
/// - `none` if the server progressed the handshake past the point where
/// client authentication is possible.
///
/// Can be called at most once per handshake. Also, this method fails if
/// the handshake has already progressed too far for this to be possible.
///
/// The returned future and `client-identity-request` are child resources
/// of the `client-handshake`.
receive-client-identity-request: func() -> result<future-client-identity-request>;
/// Perform (the remainder of) the handshake. Future resolves when the
/// handshake has completed.
///
/// The returned future is a child resource of the `client-connection`.
finish: static func(this: client-handshake) -> future-streams;
/// Cancel the handshake because of an error. The returned pollable
/// resolves when the error alert has been written out.
///
/// The pollable is a child resource of the `client-connection`.
///
/// TODO: add reason/errorcode parameter?
abort: static func(this: client-handshake) -> pollable;
}
/// A TLS server connection.
///
/// The various properties on this type become available as the handshake
/// progresses.
///
/// # Design
/// TLS connections can be divided up into roughly two phases: the handshake
/// phase and the bulk data transfer phase. (TLS renegotiation is not exposed
/// in this interface.)
/// By far, most of the API complexity is in the handshake phase, which has
/// been split off into a separate `server-handshake` type. The
/// `server-handshake` is used to configure and control advanced (but optional)
/// operations such as deriving server settings from the client hello or
/// requesting and validating a client certificate.
/// Once the `server-handshake` has been set up appropriately, the handshake
/// can be completed using the `finish` method. That consumes the
/// `server-handshake` and gives a pair of cleartext streams back in return.
///
/// The `server-connection` and its handshake resource don't not perform
/// any I/O on their own. It is a pure stream "wrapper" that consumes raw
/// TLS data streams and exposes cleartext streams.
/// The `input` and `output` parameters of constructor map to the
/// raw TLS data streams and are typically obtained from a WASI TCP socket.
/// The `server-handshake::finish` method returns the cleartext streams.
///
/// ## Consumer drives the handshake
/// Many TLS libraries let users inject custom behavior at various points
/// of interest through the registration of callbacks.
/// The WebAssembly Component Model does not support callbacks. Instead, the
/// WASI TLS interface exposes direct-style APIs for the consumer to actively
/// drive the handshake forwards.
///
/// The `server-handshake` starts out "suspended". The consumer then drives
/// the handshake to the next point of interest. For example using the
/// `receive-client-hello` method. Calling it:
/// - resumes the handshake,
/// - asynchronously waits for the client to send its hello message,
/// - upon receipt suspends the handshake again
/// - resolves the returned future.
/// It is then back up to the consumer to drive the handshake along to the
/// next point of interest (e.g. `request-client-identity`)
/// or finish the handshake entirely (`finish`).
///
/// No data flows through the I/O streams while a server-handshake is suspended.
///
/// # Usage
/// The general usage pattern looks like this:
/// - Construct a new connection using the `server-connection` constructor.
/// - Obtain the handshake resource using `server-connection::accept`. The
/// handshake starts out suspended.
/// - (Optional) Configure the settings using the various
/// `configure-*` methods. These may only be called while the
/// handshake is suspended.
/// - (Optional) Resume the handshake and wait for the client to send its
/// handshake using: `receive-client-hello`.
/// - (Optional) Further refine the settings based on the received
/// client hello using the `configure-*` methods.
/// - (Optional) Resume the handshake by requesting a client certificate and
/// waiting for it using: `request-client-identity`.
/// - Resume and run the handshake to completion using: `finish`.
/// - Read & write application data into the streams returned by `finish`.
///
/// The optional steps must happen in the order as defined above.
///
/// ## Example
/// A minimal, but production-ready, example in pseudocode:
/// ```text
/// let my-identity = ...;
///
/// let conn = new server-connection(tcp-input, tcp-output);
/// let handshake = conn.accept();
/// handshake.configure-server-identities([my-identity]);
/// let (tls-input, tls-output) = handshake.finish().await?;
/// ```
///
/// This assumes `tcp-input` and `tcp-output` are input/output streams
/// obtained from e.g. `tcp-socket::accept`.
///
/// # Secure by default
/// Implementations should pick reasonably safe defaults for all security
/// related settings. Users of this interface should be able to confidently
/// instantiate a new server connection and then, after configuring only the
/// server identity, immediately initiate the handshake. As demonstrated in
/// the example above.
resource server-connection {
// TODO: graceful shutdown
/// Create a new connection instance that wraps the provided
/// raw TLS data streams.
constructor(input: input-stream, output: output-stream);
/// Create a new server handshake. This method may be called at most once.
/// This method itself does not perform any I/O. Use the returned resource
/// to actually drive the handshake forwards.
///
/// The returned `server-handshake` is a child resources of the `server-connection`.
accept: func() -> result<server-handshake>;
/// Request post-handshake authentication. See `server-handshake::request-client-identity`.
request-client-identity: func() -> result<future-public-identity>;
/// The server name sent by the client or `none` if the client doesn't
/// support SNI.
server-name: func() -> option<string>;
/// The negotiated ALPN ID, if any.
///
/// Returns `none` when:
/// - the client did not advertise any ALPN IDs, or:
/// - there was no intersection between the IDs advertised by the client
/// and the IDs supported by the server.
alpn-id: func() -> option<alpn-id>;
/// The client's identity accepted by the server, if any.
///
/// Returns `none` when:
/// - the server did not request a client certificate,
/// - the server did request a client certificate, but the client didn't
/// respond with a valid certificate.
client-identity: func() -> option<public-identity>;
/// The certificate of the server.
/// This is one of the identities passed to `server-handshake::configure-server-identities`.
server-identity: func() -> option<private-identity>;
/// The negotiated TLS protocol version.
protocol-version: func() -> option<protocol-version>;
/// The negotiated cipher suite.
cipher-suite: func() -> option<cipher-suite>;
}
/// Resource to control a TLS server handshake.
///
/// See `server-connection` for more information.
resource server-handshake {
/// Configure which ALPN IDs the server is willing to accept from
/// the client, in descending order of preference.
configure-alpn-ids: func(value: list<alpn-id>) -> result;
/// Configure the server certificates, in descending order of preference.
///
/// The TLS implementation will select the best match from this list
/// based on parameters submitted in the client hello. Not all of those
/// parameters may be exposed in the `client-hello` resource.
configure-server-identities: func(value: list<borrow<private-identity>>) -> result;
// TODO: configure-protocol-versions: func(value: list<protocol-version>) -> result;
// TODO: configure-cipher-suites: func(value: list<cipher-suite>) -> result;
/// (Optional) Partially continue the handshake and wait for the
/// ClientHello message to be received.
///
/// Can be called at most once per handshake. Also, this method fails if
/// the handshake has already progressed too far for this to be possible.
///
/// The returned future and `client-hello` are child resources of
/// the `server-handshake`.
receive-client-hello: func() -> result<future-client-hello>;
/// (Optional) Partially continue the handshake and request the client
/// to provide a certificate. Future resolves when the client's response
/// has been received and validated.
///
/// Can be called at most once per handshake. Also, this method fails if
/// the handshake has already progressed too far for this to be possible.
///
/// The returned future is a child resources of the `server-handshake`.
///
/// TODO: custom set of root certificates
/// TODO: identity required true/false
/// TODO: Add ability to specify requirements such as: authorities, supported-signature-algorithms, oid-filters, ..
/// TODO: disable built-in validations (SSL_VERIFY_NONE). Should this still send the authorities?
request-client-identity: func() -> result<future-public-identity>;
/// Perform the remainder of the handshake. Future resolves when the
/// handshake has completed.
///
/// The returned future is a child resource of the `server-connection`.
finish: static func(this: server-handshake) -> future-streams;
/// Cancel the handshake because of an error. The returned pollable
/// resolves when the error alert has been written out.
///
/// The pollable is a child resource of the `server-connection`.
///
/// TODO: add reason/errorcode parameter?
abort: static func(this: server-handshake) -> pollable;
}
resource client-hello {
// TODO: expose the requested TLS version(s)? TLS1.2 and lower only communicates the highest version. TLS1.3 and higher communicates an exact list of versions.
/// The server name sent by the client or `none` if the client doesn't
/// support SNI.
server-name: func() -> option<string>;
/// The ALPN IDs advertised by the client. Returns an empty list if the
/// client didn't provide any IDs or doesn't support ALPN.
alpn-ids: func() -> list<alpn-id>;
/// The supported cipher suites of the client.
cipher-suites: func() -> list<cipher-suite>;
}
/// Dropping the request is equivalent to offering no certificate.
resource client-identity-request {
// TODO: Add ability to get the requirements sent by the server. Such as: authorities, supported-signature-algorithms, oid-filters, ..
/// TODO
respond: static func(this: client-identity-request, identities: list<borrow<private-identity>>);
}
// Boilerplate:
/// `future<tuple<input-stream, output-stream>>`
resource future-streams {
subscribe: func() -> pollable;
get: func() -> option<result<result<tuple<input-stream, output-stream>>>>;
}
/// `future<client-hello>`
resource future-client-hello {
subscribe: func() -> pollable;
get: func() -> option<result<result<client-hello>>>;
}
/// `future<option<public-identity>>`
resource future-public-identity {
subscribe: func() -> pollable;
get: func() -> option<result<result<option<public-identity>>>>;
}
/// `future<option<client-identity-request>>`
resource future-client-identity-request {
subscribe: func() -> pollable;
get: func() -> option<result<result<option<client-identity-request>>>>;
}
}