Skip to content

Commit 4969fbe

Browse files
committed
WIP
1 parent 061d28f commit 4969fbe

File tree

1 file changed

+308
-0
lines changed

1 file changed

+308
-0
lines changed

wit/tls.wit

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
interface tls {
2+
use wasi:io/streams@0.2.0.{input-stream, output-stream};
3+
use wasi:io/poll@0.2.0.{pollable};
4+
5+
/// TLS protocol version.
6+
///
7+
/// At the time of writing, these are the existing TLS versions:
8+
/// - 0x0200: SSLv2 (Deprecated)
9+
/// - 0x0300: SSLv3 (Deprecated)
10+
/// - 0x0301: TLSv1.0 (Deprecated)
11+
/// - 0x0302: TLSv1.1 (Deprecated)
12+
/// - 0x0303: TLSv1.2
13+
/// - 0x0304: TLSv1.3
14+
///
15+
/// TODO: Want to use regular WIT `enum` type, but then adding a new protocol is backwards incompatible.
16+
type protocol-version = u16;
17+
18+
/// Application-Layer Protocol Negotiation (ALPN) protocol ID.
19+
///
20+
/// ALPN IDs are between 1 and 255 bytes long. Typically, they represent the
21+
/// binary encoding of an ASCII string (e.g. `[0x68 0x32]` for `"h2"`),
22+
/// though this is not required.
23+
type alpn-id = list<u8>;
24+
25+
/// A X509 certificate chain; starting with the end-entity's certificate
26+
/// followed by 0 or more intermediate certificates.
27+
resource public-identity {
28+
export-X509-chain: func() -> list<list<u8>>;
29+
}
30+
31+
/// The combination of a private key with its public certificate(s).
32+
/// The private key data can not be exported.
33+
resource private-identity {
34+
parse: static func(private-key: list<u8>, x509-chain: list<list<u8>>) -> result<private-identity>;
35+
36+
public-identity: func() -> public-identity;
37+
}
38+
39+
/// Resource to configure, control & observe a TLS client stream.
40+
///
41+
/// # Transform stream
42+
/// A TLS client resource does not perform any I/O on its own. It is a pure
43+
/// stream transformer that takes cleartext data on one side and emits
44+
/// secured TLS data on the other side and vice-versa.
45+
/// The user of this resource is responsible for continually
46+
/// "pumping" the data from the underlying socket into the TLS client's
47+
/// `public-output` stream and the `public-input` back into the socket.
48+
///
49+
/// # Usage
50+
/// The general usage pattern looks something like this:
51+
/// - Instantiate new `client`.
52+
/// - (Optional) Further refine the settings using the various `configure-*` methods.
53+
/// - Forward the "public" streams acquired in the previous step into the underlying socket.
54+
/// - Call `resume`.
55+
/// - Read & write application data into the "private" streams.
56+
///
57+
/// # Suspend / resume
58+
/// A `client` always starts out suspended. During this initial suspension
59+
/// the various `configure-*` methods may be called. After configuration,
60+
/// the TLS handshake must be manually initiated using the `resume` method.
61+
///
62+
/// Many TLS libraries let users provide custom behavior through the
63+
/// registration of callbacks. The WebAssembly Component Model does not
64+
/// support callbacks. Instead, these customization points are modeled as
65+
/// additional suspensions.
66+
///
67+
/// The `suspend-at` parameter of the `connect` constructor controls at which
68+
/// moments during the lifetime of the TLS stream the client should
69+
/// automatically suspend itself.
70+
///
71+
/// The consumer drives this transition using the `suspend` method. If the
72+
/// client is not ready to be suspended, the pollable returned by `subscribe`
73+
/// can be used to wait for its readiness. When `suspend` succeeds, the client
74+
/// is suspended and some settings are open for configuration again. Once
75+
/// ready to continue the connection, the consumer should call `resume`.
76+
///
77+
/// In general:
78+
/// - While a client is suspended, no data flows through the I/O streams.
79+
/// - A client may only be configured while it is suspended.
80+
///
81+
/// # Secure by default
82+
/// Implementations should pick reasonably safe defaults for all security related
83+
/// settings. Users of this interface should be able to confidently instantiate
84+
/// a new `client` and then, without further configuration, immediately
85+
/// initiate the handshake.
86+
resource client {
87+
/// Create a new suspended TLS client.
88+
constructor(server-name: string, suspend-at: client-suspension-points);
89+
90+
/// Obtain the I/O streams associated with this client.
91+
/// These must be obtained exactly once, before the first call to `resume`.
92+
///
93+
/// Returns an error if they were already obtained before.
94+
///
95+
/// The I/O streams are child resources of the client. They must be
96+
/// dropped before the client is dropped.
97+
streams: func() -> result<io-streams>;
98+
99+
100+
101+
102+
103+
/// Configure the ALPN IDs for this client to adertise to the server,
104+
/// in descending order of preference.
105+
///
106+
/// This may only be configured while suspended in the `constructed` phase.
107+
configure-alpn-ids: func(value: list<alpn-id>) -> result;
108+
109+
/// Configure the client certificates, in descending order of preference.
110+
///
111+
/// If the server requests a certificate from the client,
112+
/// the TLS implementation will consult this list of configured identities
113+
/// in the provided order and pick the first one that satisfies the
114+
/// constraints sent by the server. If there was no match, then by default
115+
/// it is up to the server to decide whether to continue or abort the connection.
116+
///
117+
/// This may only be configured while suspended in the `constructed`,
118+
/// `verify-server-identity` or `select-client-identity` phases.
119+
configure-identities: func(value: list<borrow<private-identity>>) -> result;
120+
121+
122+
123+
124+
125+
/// The server name that was provided during construction of this client.
126+
server-name: func() -> string;
127+
128+
/// The negotiated ALPN ID, if any.
129+
///
130+
/// Returns `none` when:
131+
/// - the handshake did not take place yet,
132+
/// - the client did not advertise any ALPN IDs,
133+
/// - a successful handshake has occurred, but there was no intersection
134+
/// between the IDs advertised by the client and the IDs supported by
135+
/// the server.
136+
alpn-id: func() -> option<alpn-id>;
137+
138+
/// The negotiated TLS protocol version.
139+
///
140+
/// Returns `none` if the handshake did not take place yet.
141+
protocol-version: func() -> option<protocol-version>;
142+
143+
/// The client's identity advertised to the server, if any.
144+
/// This will be one of the identities configured using `configure-identities`.
145+
///
146+
/// This becomes available _after_ the `select-client-identity` phase.
147+
///
148+
/// Returns `none` when:
149+
/// - the handshake did not take place yet,
150+
/// - the server did not request a client certificate,
151+
/// - the server did request a client certificate, but there was no match
152+
/// with the configured identities.
153+
client-identity: func() -> option<private-identity>;
154+
155+
/// The validated certificate of the server.
156+
///
157+
/// This becomes available _after_ the `verify-server-identity` phase.
158+
server-identity: func() -> option<public-identity>;
159+
160+
161+
162+
163+
164+
/// Attempt to suspend the client at one of the places specified by
165+
/// `suspend-at` during construction of the client.
166+
///
167+
/// Returns `error(not-ready)` if the client is not ready to be suspended.
168+
/// Use the pollable returned by `subscribe` to wait for its readiness.
169+
///
170+
/// Returns `error(already-suspended)` is already suspended.
171+
///
172+
/// Returns `error(closed)` if the connection has shut down either
173+
/// successfully or abornmally.
174+
///
175+
/// The suspension resource is a child resource of the client. Dropping
176+
/// the `client` while it still has an active suspension resource may trap.
177+
suspend: func() -> result<client-suspension, suspend-error>;
178+
179+
/// Resume the suspended client. Returns an error if the client is not
180+
/// suspended.
181+
///
182+
/// If the client was suspended though `suspend` (i.e. it is not the
183+
/// initial resumption), this method will trap if the suspension
184+
/// resource hasn't been dropped yet.
185+
///
186+
/// Returns an error if the client is not suspended.
187+
resume: func() -> result;
188+
189+
/// Create a `pollable` which can be used to poll for
190+
/// the client to be suspendable or closed.
191+
///
192+
/// `subscribe` only has to be called once per client and can be (re)used
193+
/// for the remainder of the client's lifetime.
194+
subscribe: func() -> pollable;
195+
}
196+
197+
/// The I/O streams that represent both sides of the transform.
198+
///
199+
/// The application side interacts with the cleartext "private" streams.
200+
/// The network side interacts with the encrypted "public" streams.
201+
///
202+
/// A typical setup looks something like this:
203+
///
204+
/// ```text
205+
/// : TCP Socket TLS Client/Server
206+
/// +-----------------+ +---------------------------------------------+
207+
/// | | splice | decryption | read
208+
/// | `input-stream` | ========>> | `public-output` =========>> `private-input` | ======>> your
209+
/// | | | | app
210+
/// | | | | lives
211+
/// | `output-stream` | <<======== | `public-input` <<========= `private-output` | <<====== here
212+
/// | | splice | encryption | write
213+
/// +-----------------+ +---------------------------------------------+
214+
/// ```
215+
///
216+
/// The user of this interface is responsible for continually forwarding
217+
/// data from the socket into the `public-output` stream and
218+
/// data from the `public-input` into the socket.
219+
///
220+
/// # Caution
221+
/// Because the guest acts as both the producer and the consumer for these
222+
/// streams, do not use the `blocking_*` methods as that will deadlock yourself.
223+
record io-streams {
224+
public-input: input-stream,
225+
public-output: output-stream,
226+
private-output: output-stream,
227+
private-input: input-stream,
228+
}
229+
230+
flags client-suspension-points {
231+
/// When the client received the server's certificate.
232+
verify-server-identity,
233+
234+
/// When the client received a certificate request from the server.
235+
select-client-identity,
236+
237+
/// When the initial handshake was successful.
238+
connected,
239+
}
240+
241+
resource client-suspension {
242+
/// At which point the TLS stream is suspended.
243+
/// Exactly one flag is set in the return value.
244+
at: func() -> client-suspension-points;
245+
246+
/// Only for select-client-identity:
247+
// TODO: acceptable-authorities: func() -> result<list<string>>;
248+
249+
/// Only for verify-server-identity:
250+
// TODO: unverified-identity: func() -> result<public-identity>;
251+
}
252+
253+
enum suspend-error {
254+
not-ready,
255+
already-suspended,
256+
closed,
257+
}
258+
259+
260+
261+
262+
263+
264+
265+
266+
267+
// TODO
268+
resource server {
269+
constructor(suspend-at: server-suspension-points);
270+
streams: func() -> result<io-streams>;
271+
272+
configure-alpn-ids: func(value: list<alpn-id>) -> result;
273+
configure-identities: func(value: list<borrow<private-identity>>) -> result;
274+
275+
server-name: func() -> option<string>;
276+
alpn-id: func() -> option<alpn-id>;
277+
protocol-version: func() -> option<protocol-version>;
278+
client-identity: func() -> option<public-identity>;
279+
server-identity: func() -> option<private-identity>;
280+
281+
suspend: func() -> result<server-suspension, suspend-error>;
282+
resume: func() -> result;
283+
subscribe: func() -> pollable;
284+
}
285+
286+
flags server-suspension-points {
287+
/// When the server received the initial message from the client.
288+
client-hello,
289+
290+
/// When the server received the client's certificate.
291+
verify-client-identity,
292+
293+
/// When the initial handshake was successful.
294+
accepted,
295+
}
296+
297+
resource server-suspension {
298+
at: func() -> server-suspension-points;
299+
300+
/// Only for client-hello:
301+
// TODO: requested-protocol-versions: func() -> result<list<protocol-version>>;
302+
// TODO: requested-server-name: func() -> result<option<string>>;
303+
// TODO: requested-alpn-ids: func() -> result<list<alpn-id>>;
304+
305+
/// Only for verify-client-identity:
306+
// TODO: unverified-identity: func() -> result<public-identity>;
307+
}
308+
}

0 commit comments

Comments
 (0)