Skip to content

Commit 3b636b0

Browse files
author
Stefan Eissing
committed
Client Hello carries not SNI, signature schemes and ALPN protocols.
- Added a base.rs for structs providing strings, string arrays and short arrays to C callback functions. - Extended rustls_client_hello, adapted the prototype and added documentation. - Made a section about EXPERIMENTAL features in the README.md
1 parent 5c6d1ea commit 3b636b0

File tree

6 files changed

+301
-41
lines changed

6 files changed

+301
-41
lines changed

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,41 @@ Functions that are theoretically infallible don't return rustls_result, so we
114114
can't return RUSTLS_RESULT_PANIC. In those cases, if there's a panic, we'll
115115
return a default value suitable to the return type: NULL for pointer types,
116116
false for bool types, and 0 for integer types.
117+
118+
# Experimentals
119+
120+
Several features of the C bindings are marked as `EXPERIMENTAL` as they are
121+
need further evaluation and will most likely change significantly in the future.
122+
123+
## Server Side Experimentals
124+
125+
The `rustls_server_config_builder_set_hello_callback` and its provided information
126+
in `rustls_client_hello` will change. The current design is a snapshot of the
127+
implementation efforts in [mod_tls](https://github.com/icing/mod_tls) to provide
128+
`rustls` base TLS as module for the Apache webserver.
129+
130+
For a webserver hosting multiple domains on the same endpoint, it is highly desirable
131+
to have individual TLS settings, depending on the domain the client wants to talk to.
132+
Most domains have their own TLS certificates, some have configured restrictions on
133+
other features as well, such as TLS protocol versions, ciphers or client authentication.
134+
135+
The approach to this taken with the current `rustls_client_hello` is as follows:
136+
137+
#### One domain, one cert
138+
139+
If you have a single site and one certificate, you can preconfigure the `rustls_server_config` accordingly and do not need to register any callback.
140+
141+
#### Multiple domains/certs/settings
142+
143+
If you need to support multiple `rustls_server_config`s on the same connection endpoint, you can start the connection with a default `rustls_server_config` and register a client hello callback. The callback inspects the SNI/ALPN/cipher values announced by the client and selects the appropriate configuration to use.
144+
145+
When your callback returns, the handshake of `rustls` will fail, as no certifcate was configured. This will be noticeable as an error returned from `rustls_server_session_write_tls()`. You can then free this session
146+
and create the one with the correct setting for the domain chosen.
147+
148+
For this to work, your connection needs ot buffer the initial data from the client, so these bytes can be
149+
replayed to the second session you use. Do not write any data back to the client while your are
150+
in the initial session. The client hellos are usually only a few hundred bytes.
151+
152+
153+
154+

src/base.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use libc::size_t;
2+
use std::os::raw::{c_char, c_ushort};
3+
4+
/// A read-only view on a Rust utf-8 string or byte array.
5+
/// This is used to pass data from crustls to callback functions provided
6+
/// by the user of the API. The `data` is not NUL-terminated.
7+
/// `len` indicates the number of bytes than can be safely read.
8+
/// A `len` of 0 is used to represent a missing value.
9+
///
10+
/// The memmory exposed is available for the duration of the call (e.g.
11+
/// when passed to a callback) and must be copied if the values are
12+
/// needed for longer.
13+
///
14+
#[allow(non_camel_case_types)]
15+
#[repr(C)]
16+
pub struct rustls_string {
17+
data: *const c_char,
18+
len: size_t,
19+
}
20+
21+
impl<'a> From<&'a String> for rustls_string {
22+
fn from(s: &String) -> Self {
23+
rustls_string {
24+
data: s.as_ptr() as *const c_char,
25+
len: s.len() as size_t,
26+
}
27+
}
28+
}
29+
30+
impl<'a> From<&'a str> for rustls_string {
31+
fn from(s: &str) -> Self {
32+
rustls_string {
33+
data: s.as_ptr() as *const c_char,
34+
len: s.len() as size_t,
35+
}
36+
}
37+
}
38+
39+
impl<'a> From<&'a [u8]> for rustls_string {
40+
fn from(s: &[u8]) -> Self {
41+
rustls_string {
42+
data: s.as_ptr() as *const c_char,
43+
len: s.len() as size_t,
44+
}
45+
}
46+
}
47+
48+
pub(crate) fn rustls_strings<'a>(values: Option<&'a [&'a [u8]]>) -> Vec<rustls_string> {
49+
let mut strings: Vec<rustls_string> = Vec::new();
50+
match values {
51+
Some(values) => {
52+
for entry in values.iter() {
53+
strings.push(rustls_string::from(*entry))
54+
}
55+
}
56+
None => (),
57+
};
58+
strings
59+
}
60+
61+
/// A read-only view on a list of Rust utf-8 strings or byte arrays.
62+
/// This is used to pass data from crustls to callback functions provided
63+
/// by the user of the API. The `data` is an array of `rustls_string`
64+
/// structures with `len` elements.
65+
///
66+
/// The memmory exposed is available for the duration of the call (e.g.
67+
/// when passed to a callback) and must be copied if the values are
68+
/// needed for longer.
69+
///
70+
#[allow(non_camel_case_types)]
71+
#[repr(C)]
72+
pub struct rustls_vec_string {
73+
data: *const rustls_string,
74+
len: size_t,
75+
}
76+
77+
impl<'a> From<&'a Vec<rustls_string>> for rustls_vec_string {
78+
fn from(values: &Vec<rustls_string>) -> Self {
79+
rustls_vec_string {
80+
data: values.as_ptr(),
81+
len: values.len(),
82+
}
83+
}
84+
}
85+
86+
/// A read-only view on a list of Rust owned unsigned short values.
87+
/// This is used to pass data from crustls to callback functions provided
88+
/// by the user of the API. The `data` is an array of `len` 16 bit values.
89+
///
90+
/// The memmory exposed is available for the duration of the call (e.g.
91+
/// when passed to a callback) and must be copied if the values are
92+
/// needed for longer.
93+
///
94+
#[allow(non_camel_case_types)]
95+
#[repr(C)]
96+
pub struct rustls_vec_ushort {
97+
data: *const c_ushort,
98+
len: size_t,
99+
}
100+
101+
impl From<&Vec<u16>> for rustls_vec_ushort {
102+
fn from(values: &Vec<u16>) -> Self {
103+
rustls_vec_ushort {
104+
data: values.as_ptr(),
105+
len: values.len(),
106+
}
107+
}
108+
}

src/cipher.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ use crate::{ffi_panic_boundary_generic, ffi_panic_boundary_unit};
44
use libc::{c_char, size_t};
55
use std::os::raw::c_ushort;
66

7-
static ALL_SIGNATURE_SCHEMES: &[rustls::SignatureScheme] = &[
7+
/// All SignatureScheme currently defines in rustls.
8+
/// At the moment not exposed by rustls itself.
9+
#[no_mangle]
10+
pub(crate) static ALL_SIGNATURE_SCHEMES: &[rustls::SignatureScheme] = &[
811
rustls::SignatureScheme::RSA_PKCS1_SHA1,
912
rustls::SignatureScheme::ECDSA_SHA1_Legacy,
1013
rustls::SignatureScheme::RSA_PKCS1_SHA256,
@@ -20,25 +23,39 @@ static ALL_SIGNATURE_SCHEMES: &[rustls::SignatureScheme] = &[
2023
rustls::SignatureScheme::ED448,
2124
];
2225

26+
/// rustls has the names in its Debug trait implementation, which
27+
/// we use for all known schemes. For all others we return the hex value.
28+
/// Note that this u16 values are used in protocol handshake by both sides,
29+
/// so we have to expect unknown values to arrive here.
2330
fn signature_scheme_name(n: u16) -> String {
2431
for scheme in ALL_SIGNATURE_SCHEMES {
2532
if scheme.get_u16() == n {
2633
return format!("{:?}", scheme);
2734
}
2835
}
29-
String::from("Unknown")
36+
format!("Unknown({:#06x})", n)
3037
}
3138

32-
pub(crate) fn map_signature_schemes(schemes: &[rustls::SignatureScheme]) -> Vec<u16> {
39+
/// Collect the u16 values of the given SignatureScheme slice, so they
40+
/// can be exposed in our API.
41+
pub(crate) fn rustls_cipher_map_signature_schemes(schemes: &[rustls::SignatureScheme]) -> Vec<u16> {
3342
let mut mapped_schemes: Vec<u16> = Vec::new();
3443
for s in schemes {
3544
mapped_schemes.push(s.get_u16());
3645
}
3746
mapped_schemes
3847
}
3948

49+
/// Get the name of a SignatureScheme, represented by the `scheme` short value,
50+
/// if known by the rustls library. For unknown schemes, this returns a string
51+
/// with the scheme value in hex notation.
52+
/// Mainly useful for debugging output.
53+
/// The caller provides `buf` for holding the string and gives its size as `len`
54+
/// bytes. On return `out_n` carries the number of bytes copied into `buf`. The
55+
/// `buf` is not NUL-terminated.
56+
///
4057
#[no_mangle]
41-
pub extern "C" fn rustls_signature_scheme_name(
58+
pub extern "C" fn rustls_cipher_get_signature_scheme_name(
4259
scheme: c_ushort,
4360
buf: *mut c_char,
4461
len: size_t,

src/crustls.h

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,76 @@ typedef struct rustls_server_session rustls_server_session;
134134
*/
135135
typedef void *rustls_client_hello_userdata;
136136

137+
/**
138+
* A read-only view on a Rust utf-8 string or byte array.
139+
* This is used to pass data from crustls to callback functions provided
140+
* by the user of the API. The `data` is not NUL-terminated.
141+
* `len` indicates the number of bytes than can be safely read.
142+
* A `len` of 0 is used to represent a missing value.
143+
*
144+
* The memmory exposed is available for the duration of the call (e.g.
145+
* when passed to a callback) and must be copied if the values are
146+
* needed for longer.
147+
*
148+
*/
149+
typedef struct rustls_string {
150+
const char *data;
151+
size_t len;
152+
} rustls_string;
153+
154+
/**
155+
* A read-only view on a list of Rust owned unsigned short values.
156+
* This is used to pass data from crustls to callback functions provided
157+
* by the user of the API. The `data` is an array of `len` 16 bit values.
158+
*
159+
* The memmory exposed is available for the duration of the call (e.g.
160+
* when passed to a callback) and must be copied if the values are
161+
* needed for longer.
162+
*
163+
*/
164+
typedef struct rustls_vec_ushort {
165+
const unsigned short *data;
166+
size_t len;
167+
} rustls_vec_ushort;
168+
169+
/**
170+
* A read-only view on a list of Rust utf-8 strings or byte arrays.
171+
* This is used to pass data from crustls to callback functions provided
172+
* by the user of the API. The `data` is an array of `rustls_string`
173+
* structures with `len` elements.
174+
*
175+
* The memmory exposed is available for the duration of the call (e.g.
176+
* when passed to a callback) and must be copied if the values are
177+
* needed for longer.
178+
*
179+
*/
180+
typedef struct rustls_vec_string {
181+
const struct rustls_string *data;
182+
size_t len;
183+
} rustls_vec_string;
184+
137185
/**
138186
* The TLS Client Hello information provided to a ClientHelloCallback function.
139-
* `sni_name` is the SNI servername provided by the client (not 0 terminated) with
140-
* `sni_name_len` as its length. If the client did not provide an SNI, the name
141-
* length is 0.
187+
* `sni_name` is the SNI servername provided by the client. If the client
188+
* did not provide an SNI, the length of this `rustls_string` will be 0.
189+
* The signature_schemes carries the values supplied by the client or, should
190+
* the client not use this TLS extension, the default schemes in the rustls
191+
* library. See:
192+
* https://docs.rs/rustls/0.19.0/rustls/internal/msgs/enums/enum.SignatureScheme.html
193+
* `alpn` carries the list of ALPN protocol names that the client proposed to
194+
* the server. Again, the length of this list will be 0 if non were supplied.
195+
*
196+
* All this data, when passed to a callback function, is only accessible during
197+
* the call and may not be modified. Users of this API must copy any values that
198+
* they want to access when the callback returned.
199+
*
200+
* EXPERIMENTAL: this feature of crustls is likely to change in the future, as
201+
* the rustls library is re-evaluating their current approach to client hello handling.
142202
*/
143203
typedef struct rustls_client_hello {
144-
const char *sni_name;
145-
size_t sni_name_len;
146-
const unsigned short *signature_schemes;
147-
size_t signature_schemes_len;
204+
struct rustls_string sni_name;
205+
struct rustls_vec_ushort signature_schemes;
206+
struct rustls_vec_string alpn;
148207
} rustls_client_hello;
149208

150209
/**
@@ -154,6 +213,21 @@ typedef struct rustls_client_hello {
154213
*/
155214
size_t rustls_version(char *buf, size_t len);
156215

216+
/**
217+
* Get the name of a SignatureScheme, represented by the `scheme` short value,
218+
* if known by the rustls library. For unknown schemes, this returns a string
219+
* with the scheme value in hex notation.
220+
* Mainly useful for debugging output.
221+
* The caller provides `buf` for holding the string and gives its size as `len`
222+
* bytes. On return `out_n` carries the number of bytes copied into `buf`. The
223+
* `buf` is not NUL-terminated.
224+
*
225+
*/
226+
void rustls_cipher_get_signature_scheme_name(unsigned short scheme,
227+
char *buf,
228+
size_t len,
229+
size_t *out_n);
230+
157231
/**
158232
* Create a rustls_client_config_builder. Caller owns the memory and must
159233
* eventually call rustls_client_config_builder_build, then free the
@@ -438,17 +512,16 @@ enum rustls_result rustls_server_session_get_sni_hostname(const struct rustls_se
438512
* Register a callback to be invoked when a session created from this config
439513
* is seeing a TLS ClientHello message. The given `userdata` will be passed
440514
* to the callback when invoked.
441-
* Specifying `replace`!= 0 will replace any existing `ResolvesServerCert` in
442-
* the config. Otherwise, the existing `ResolvesServerCert` will be invoked
443-
* after the callback has been returned successfully.
444-
* Any error returned by the callback will abort the TLS handshake and give
445-
* an error in the called rustls function (most likely
446-
* `rustls_server_session_write_tls`).
515+
* Any existing `ResolvesServerCert` implementation currently installed in the
516+
* `rustls_server_config` will be replaced. This also means registering twice
517+
* will overwrite the first registration. It is not permitted to pass a NULL
518+
* value for `callback`, but it is possible to have `userdata` as NULL.
519+
*
520+
* EXPERIMENTAL: this feature of crustls is likely to change in the future, as
521+
* the rustls library is re-evaluating their current approach to client hello handling.
447522
*/
448523
enum rustls_result rustls_server_config_builder_set_hello_callback(struct rustls_server_config_builder *builder,
449524
void (*callback)(rustls_client_hello_userdata userdata, const struct rustls_client_hello *hello),
450525
rustls_client_hello_userdata userdata);
451526

452-
void rustls_signature_scheme_name(unsigned short scheme, char *buf, size_t len, size_t *out_n);
453-
454527
#endif /* CRUSTLS_H */

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use libc::{c_char, size_t};
33
use std::{cmp::min, sync::Arc};
44
use std::{mem, slice};
55

6+
mod base;
7+
mod cipher;
68
mod client;
79
mod error;
810
mod server;
9-
mod cipher;
1011

1112
// Keep in sync with Cargo.toml.
1213
const RUSTLS_CRATE_VERSION: &str = "0.19.0";

0 commit comments

Comments
 (0)