Skip to content

Commit b3f7fd6

Browse files
committed
Merge branch 'client_hello_callback' of github.com:icing/crustls into client_hello_callback_update_base
2 parents ae28651 + 05ad7eb commit b3f7fd6

File tree

12 files changed

+577
-64
lines changed

12 files changed

+577
-64
lines changed

.github/workflows/test.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,9 @@ jobs:
1818
- uses: actions/checkout@v2
1919
- run: make src/crustls.h
2020
- run: git diff
21+
22+
cargo-fmt:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v2
26+
- run: cargo fmt -- --check

CONTRIBUTING.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Contributing
2+
3+
Welcome to crustls! We're excited to work with you. If you've got a big chunk of
4+
work that you'd like to do, please file an issue before starting on the code, so
5+
we can get on the same page. If you've just got a small tweak, it's fine to send
6+
a PR without filing an issue first.
7+
8+
After you've sent a PR, we ask that you don't rebase, squash, or force push your
9+
branch. This makes it easier to see changes as your PR evolves. Once we approve
10+
your PR, we will squash it into a single commit, with the summary line set to
11+
the summary of your PR, and the description set to the description of your PR.
12+
That way we maintain a linear git history, where each commit corresponds to a
13+
fully reviewed PR that passed tests.
14+
15+
Check out README.md if you haven't already to find the conventions we follow.
16+
All code must be rustfmt'ed, which we enforce in CI.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,10 @@ If you have a single site and one certificate, you can preconfigure the `rustls_
142142

143143
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.
144144

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
145+
When your callback returns, the handshake of `rustls` will fail, as no certificate was configured. This will be noticeable as an error returned from `rustls_server_session_write_tls()`. You can then free this session
146146
and create the one with the correct setting for the domain chosen.
147147

148-
For this to work, your connection needs ot buffer the initial data from the client, so these bytes can be
148+
For this to work, your connection needs to buffer the initial data from the client, so these bytes can be
149149
replayed to the second session you use. Do not write any data back to the client while your are
150150
in the initial session. The client hellos are usually only a few hundred bytes.
151151

src/base.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,18 @@ pub struct rustls_vec_ushort {
121121
len: size_t,
122122
}
123123

124-
impl From<&Vec<u16>> for rustls_vec_ushort {
124+
impl<'a> From<&'a [u16]> for rustls_vec_ushort {
125+
fn from(values: &[u16]) -> Self {
126+
rustls_vec_ushort {
127+
data: values.as_ptr(),
128+
len: values.len(),
129+
}
130+
}
131+
}
132+
133+
// Should not be necessary, according to:
134+
// https://github.com/abetterinternet/crustls/pull/50#discussion_r578720430
135+
impl<'a> From<&'a Vec<u16>> for rustls_vec_ushort {
125136
fn from(values: &Vec<u16>) -> Self {
126137
rustls_vec_ushort {
127138
data: values.as_ptr(),

src/cipher.rs

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
use std::{cmp::min, slice};
22

3-
use crate::{ffi_panic_boundary_generic, ffi_panic_boundary_unit};
3+
use crate::error::rustls_result;
4+
use crate::{
5+
ffi_panic_boundary, ffi_panic_boundary_generic, ffi_panic_boundary_unit, try_ref_from_ptr,
6+
};
47
use libc::{c_char, size_t};
8+
use std::io::Cursor;
59
use std::os::raw::c_ushort;
10+
use std::sync::Arc;
611

7-
/// All SignatureScheme currently defines in rustls.
12+
use rustls::{Certificate, PrivateKey};
13+
use rustls_pemfile::{certs, pkcs8_private_keys, rsa_private_keys};
14+
15+
use rustls::sign::CertifiedKey;
16+
use rustls_result::NullParameter;
17+
18+
/// All SignatureScheme currently defined in rustls.
819
/// At the moment not exposed by rustls itself.
920
#[no_mangle]
1021
pub(crate) static ALL_SIGNATURE_SCHEMES: &[rustls::SignatureScheme] = &[
@@ -81,3 +92,110 @@ pub extern "C" fn rustls_cipher_get_signature_scheme_name(
8192
}
8293
}
8394
}
95+
96+
/// The complete chain of certificates plus private key for
97+
/// being certified against someones list of trust anchors (commonly
98+
/// called root store). Corresponds to `CertifiedKey` in the Rust API.
99+
pub struct rustls_cipher_certified_key {
100+
// We use the opaque struct pattern to tell C about our types without
101+
// telling them what's inside.
102+
// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs
103+
_private: [u8; 0],
104+
}
105+
106+
#[no_mangle]
107+
pub extern "C" fn rustls_cipher_certified_key_build(
108+
cert_chain: *const u8,
109+
cert_chain_len: size_t,
110+
private_key: *const u8,
111+
private_key_len: size_t,
112+
certified_key_out: *mut *const rustls_cipher_certified_key,
113+
) -> rustls_result {
114+
ffi_panic_boundary! {
115+
let certified_key = match certified_key_build(
116+
cert_chain, cert_chain_len, private_key, private_key_len) {
117+
Ok(key) => Box::new(key),
118+
Err(rr) => return rr,
119+
};
120+
unsafe {
121+
*certified_key_out = Arc::into_raw(Arc::new(*certified_key)) as *const _;
122+
}
123+
return rustls_result::Ok
124+
}
125+
}
126+
127+
/// "Free" a certified_key previously returned from
128+
/// rustls_cipher_certified_key_build. Since certified_key is actually an
129+
/// atomically reference-counted pointer, extant certified_key may still
130+
/// hold an internal reference to the Rust object. However, C code must
131+
/// consider this pointer unusable after "free"ing it.
132+
/// Calling with NULL is fine. Must not be called twice with the same value.
133+
#[no_mangle]
134+
pub extern "C" fn rustls_cipher_certified_key_free(config: *const rustls_cipher_certified_key) {
135+
ffi_panic_boundary_unit! {
136+
let key: &CertifiedKey = try_ref_from_ptr!(config, &mut CertifiedKey, ());
137+
// To free the certified_key, we reconstruct the Arc. It should have a refcount of 1,
138+
// representing the C code's copy. When it drops, that refcount will go down to 0
139+
// and the inner ServerConfig will be dropped.
140+
let arc: Arc<CertifiedKey> = unsafe { Arc::from_raw(key) };
141+
let strong_count = Arc::strong_count(&arc);
142+
if strong_count < 1 {
143+
eprintln!(
144+
"rustls_cipher_certified_key_free: invariant failed: arc.strong_count was < 1: {}. \
145+
You must not free the same certified_key multiple times.",
146+
strong_count
147+
);
148+
}
149+
}
150+
}
151+
152+
pub(crate) fn certified_key_build(
153+
cert_chain: *const u8,
154+
cert_chain_len: size_t,
155+
private_key: *const u8,
156+
private_key_len: size_t,
157+
) -> Result<CertifiedKey, rustls_result> {
158+
let mut cert_chain: &[u8] = unsafe {
159+
if cert_chain.is_null() {
160+
return Err(NullParameter);
161+
}
162+
slice::from_raw_parts(cert_chain, cert_chain_len as usize)
163+
};
164+
let private_key: &[u8] = unsafe {
165+
if private_key.is_null() {
166+
return Err(NullParameter);
167+
}
168+
slice::from_raw_parts(private_key, private_key_len as usize)
169+
};
170+
let mut private_keys: Vec<Vec<u8>> = match pkcs8_private_keys(&mut Cursor::new(private_key)) {
171+
Ok(v) => v,
172+
Err(_) => return Err(rustls_result::PrivateKeyParseError),
173+
};
174+
let private_key: PrivateKey = match private_keys.pop() {
175+
Some(p) => PrivateKey(p),
176+
None => {
177+
private_keys = match rsa_private_keys(&mut Cursor::new(private_key)) {
178+
Ok(v) => v,
179+
Err(_) => return Err(rustls_result::PrivateKeyParseError),
180+
};
181+
let rsa_private_key: PrivateKey = match private_keys.pop() {
182+
Some(p) => PrivateKey(p),
183+
None => return Err(rustls_result::PrivateKeyParseError),
184+
};
185+
rsa_private_key
186+
}
187+
};
188+
let signing_key = match rustls::sign::any_supported_type(&private_key) {
189+
Ok(key) => key,
190+
Err(_) => return Err(rustls_result::PrivateKeyParseError),
191+
};
192+
let parsed_chain: Vec<Certificate> = match certs(&mut cert_chain) {
193+
Ok(v) => v.into_iter().map(Certificate).collect(),
194+
Err(_) => return Err(rustls_result::CertificateParseError),
195+
};
196+
197+
Ok(rustls::sign::CertifiedKey::new(
198+
parsed_chain,
199+
Arc::new(signing_key),
200+
))
201+
}

src/client.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use rustls_result::NullParameter;
2525
/// for concurrent mutation. Under the hood, it corresponds to a
2626
/// Box<ClientConfig>.
2727
/// https://docs.rs/rustls/0.19.0/rustls/struct.ClientConfig.html
28-
#[allow(non_camel_case_types)]
2928
pub struct rustls_client_config_builder {
3029
// We use the opaque struct pattern to tell C about our types without
3130
// telling them what's inside.
@@ -36,15 +35,13 @@ pub struct rustls_client_config_builder {
3635
/// A client config that is done being constructed and is now read-only.
3736
/// Under the hood, this object corresponds to an Arc<ClientConfig>.
3837
/// https://docs.rs/rustls/0.19.0/rustls/struct.ClientConfig.html
39-
#[allow(non_camel_case_types)]
4038
pub struct rustls_client_config {
4139
// We use the opaque struct pattern to tell C about our types without
4240
// telling them what's inside.
4341
// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs
4442
_private: [u8; 0],
4543
}
4644

47-
#[allow(non_camel_case_types)]
4845
pub struct rustls_client_session {
4946
_private: [u8; 0],
5047
}

src/crustls.h

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ typedef enum rustls_result {
8989
RUSTLS_RESULT_CERT_SCT_UNKNOWN_LOG = 7323,
9090
} rustls_result;
9191

92+
/**
93+
* The complete chain of certificates plus private key for
94+
* being certified against someones list of trust anchors (commonly
95+
* called root store). Corresponds to `CertifiedKey` in the Rust API.
96+
*/
97+
typedef struct rustls_cipher_certified_key rustls_cipher_certified_key;
98+
9299
/**
93100
* A client config that is done being constructed and is now read-only.
94101
* Under the hood, this object corresponds to an Arc<ClientConfig>.
@@ -221,6 +228,22 @@ typedef struct rustls_client_hello {
221228
struct rustls_vec_slice_bytes alpn;
222229
} rustls_client_hello;
223230

231+
/**
232+
* Prototype of a callback that can be installed by the application at the
233+
* `rustls_server_config`. This callback will be invoked by a `rustls_server_session`
234+
* once the TLS client hello message has been received.
235+
* `userdata` will be supplied as provided when registering the callback.
236+
* `hello`gives the value of the available client announcements, as interpreted
237+
* by rustls. See the definition of `rustls_client_hello` for details.
238+
*
239+
* NOTE: the passed in `hello` and all its values are only availabe during the
240+
* callback invocations.
241+
*
242+
* EXPERIMENTAL: this feature of crustls is likely to change in the future, as
243+
* the rustls library is re-evaluating their current approach to client hello handling.
244+
*/
245+
typedef const struct rustls_cipher_certified_key *(*rustls_client_hello_callback)(rustls_client_hello_userdata userdata, const struct rustls_client_hello *hello);
246+
224247
/**
225248
* Write the version of the crustls C bindings and rustls itself into the
226249
* provided buffer, up to a max of `len` bytes. Output is UTF-8 encoded
@@ -243,6 +266,22 @@ void rustls_cipher_get_signature_scheme_name(unsigned short scheme,
243266
size_t len,
244267
size_t *out_n);
245268

269+
enum rustls_result rustls_cipher_certified_key_build(const uint8_t *cert_chain,
270+
size_t cert_chain_len,
271+
const uint8_t *private_key,
272+
size_t private_key_len,
273+
const struct rustls_cipher_certified_key **certified_key_out);
274+
275+
/**
276+
* "Free" a certified_key previously returned from
277+
* rustls_cipher_certified_key_build. Since certified_key is actually an
278+
* atomically reference-counted pointer, extant certified_key may still
279+
* hold an internal reference to the Rust object. However, C code must
280+
* consider this pointer unusable after "free"ing it.
281+
* Calling with NULL is fine. Must not be called twice with the same value.
282+
*/
283+
void rustls_cipher_certified_key_free(const struct rustls_cipher_certified_key *config);
284+
246285
/**
247286
* Create a rustls_client_config_builder. Caller owns the memory and must
248287
* eventually call rustls_client_config_builder_build, then free the
@@ -403,13 +442,36 @@ enum rustls_result rustls_server_config_builder_set_ignore_client_order(struct r
403442
* first.
404443
* private_key must point to a byte array of length private_key_len containing
405444
* a private key in PEM-encoded PKCS#8 or PKCS#1 format.
445+
*
446+
* EXPERIMENTAL: installing a client_hello callback will replace any
447+
* configured certified keys and vice versa. Same holds true for the
448+
* set_single_cert variant.
406449
*/
407450
enum rustls_result rustls_server_config_builder_set_single_cert_pem(struct rustls_server_config_builder *builder,
408451
const uint8_t *cert_chain,
409452
size_t cert_chain_len,
410453
const uint8_t *private_key,
411454
size_t private_key_len);
412455

456+
/**
457+
* Provide the configuration a list of certificates where the session
458+
* will select the first one that is compatible with the client's signing
459+
* capabilities. Servers that want to support ECDSA and RSA certificates
460+
* will want the ECSDA to go first in the list.
461+
*
462+
* The built configuration will keep a reference to all certified keys
463+
* provided. The client may `rustls_cipher_certified_key_free()` afterwards
464+
* without the configuration losing them. The same certified key may also
465+
* be appear in multiple configs.
466+
*
467+
* EXPERIMENTAL: installing a client_hello callback will replace any
468+
* configured certified keys and vice versa. Same holds true for the
469+
* set_single_cert variant.
470+
*/
471+
enum rustls_result rustls_server_config_builder_set_certified_keys(struct rustls_server_config_builder *builder,
472+
const struct rustls_cipher_certified_key *const *certified_keys,
473+
size_t certified_keys_len);
474+
413475
/**
414476
* Turn a *rustls_server_config_builder (mutable) into a *rustls_server_config
415477
* (read-only).
@@ -534,9 +596,11 @@ enum rustls_result rustls_server_session_get_sni_hostname(const struct rustls_se
534596
*
535597
* EXPERIMENTAL: this feature of crustls is likely to change in the future, as
536598
* the rustls library is re-evaluating their current approach to client hello handling.
599+
* Installing a client_hello callback will replace any configured certified keys
600+
* and vice versa. Same holds true for the set_single_cert variant.
537601
*/
538602
enum rustls_result rustls_server_config_builder_set_hello_callback(struct rustls_server_config_builder *builder,
539-
void (*callback)(rustls_client_hello_userdata userdata, const struct rustls_client_hello *hello),
603+
rustls_client_hello_callback callback,
540604
rustls_client_hello_userdata userdata);
541605

542606
#endif /* CRUSTLS_H */

src/error.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ pub extern "C" fn rustls_result_is_cert_error(result: rustls_result) -> bool {
4545
}
4646
}
4747

48-
#[allow(non_camel_case_types)]
4948
#[repr(C)]
5049
pub enum rustls_result {
5150
Ok = 7000,

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![crate_type = "staticlib"]
2+
#![allow(non_camel_case_types)]
23
use libc::{c_char, size_t};
34
use std::{cmp::min, sync::Arc};
45
use std::{mem, slice};

src/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ const char *ws_strerror (int err)
8080
* we finish or hit an error. Assumes fd is blocking and therefore doesn't
8181
* handle EAGAIN. Returns 0 for success or 1 for error.
8282
*
83-
* For Windsock we cannot use a socket-fd in write().
83+
* For Winsock we cannot use a socket-fd in write().
8484
* Call send() if fd > STDOUT_FILENO.
8585
*/
8686
int

0 commit comments

Comments
 (0)