Skip to content

Commit 0983bee

Browse files
authored
feature(bindings): scheduled renegotiation via poll_recv (#4764)
1 parent 2c23194 commit 0983bee

File tree

6 files changed

+709
-135
lines changed

6 files changed

+709
-135
lines changed

.github/workflows/ci_rust.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,20 @@ jobs:
4444
- name: Generate
4545
run: ${{env.ROOT_PATH}}/generate.sh
4646

47-
- name: Tests
48-
working-directory: ${{env.ROOT_PATH}}
49-
# Test all features except for FIPS, which is tested separately.
50-
run: cargo test --features unstable-renegotiate,unstable-fingerprint,unstable-ktls,quic,pq
51-
5247
# Ensure that all tests pass with the default feature set
5348
- name: Default Tests
5449
working-directory: ${{env.ROOT_PATH}}
5550
run: cargo test
5651

52+
- name: "Feature Tests: Fingerprint, kTLS, QUIC, and PQ"
53+
working-directory: ${{env.ROOT_PATH}}
54+
# Test all features except for FIPS, which is tested separately.
55+
run: cargo test --features unstable-fingerprint,unstable-ktls,quic,pq
56+
57+
- name: "Feature Test: Renegotiate"
58+
working-directory: ${{env.ROOT_PATH}}
59+
run: cargo test --features unstable-renegotiate
60+
5761
- name: Test external build
5862
# if this test is failing, make sure that api headers are appropriately
5963
# included. For a symbol to be visible in a shared lib, the

api/unstable/renegotiate.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,31 @@
2929
* s2n-tls clients support secure (compliant with RFC5746) renegotiation for compatibility reasons,
3030
* but s2n-tls does NOT recommend its use. While s2n-tls addresses all currently known security concerns,
3131
* renegotiation has appeared in many CVEs and was completely removed from TLS1.3.
32+
*
33+
* A basic renegotiation integration with s2n-tls would look like:
34+
* 1. The application calls s2n_recv as part of normal IO.
35+
* 2. s2n_recv receives a request for renegotiation (a HelloRequest message)
36+
* instead of application data.
37+
* 3. s2n_recv calls the configured s2n_renegotiate_request_cb.
38+
* 4. The application's implementation of the s2n_renegotiate_request_cb should:
39+
* 1. Set the `response` parameter to S2N_RENEGOTIATE_ACCEPT
40+
* 2. Set some application state to indicate that renegotiation is required.
41+
* s2n_connection_set_ctx can be used to associate application state with
42+
* a specific connection.
43+
* 3. Return success.
44+
* 5. s2n_recv returns as part of normal IO.
45+
* 6. The application should check the application state set in 4.2 to determine
46+
* whether or not renegotiation is required.
47+
* 7. The application should complete any in-progress IO. Failing to do this will
48+
* cause s2n_renegotiate_wipe to fail.
49+
* 1. For sending, the application must retry any blocked calls to s2n_send
50+
* until they return success.
51+
* 2. For receiving, the application must call s2n_recv to handle any buffered
52+
* decrypted application data. s2n_peek indicates how much data is buffered.
53+
* 8. The application should call s2n_renegotiate_wipe.
54+
* 9. The application should reconfigure the connection, if necessary.
55+
* 10. The application should call s2n_renegotiate until it indicates success,
56+
* while handling any application data encountered.
3257
*/
3358

3459
/**
@@ -92,7 +117,7 @@ S2N_API int s2n_config_set_renegotiate_request_cb(struct s2n_config *config, s2n
92117
*
93118
* The application MUST handle any incomplete IO before calling this method. The last call to `s2n_send` must
94119
* have succeeded, and `s2n_peek` must return zero. If there is any data in the send or receive buffers,
95-
* this method will fail.
120+
* this method will fail. That means that this method cannot be called from inside s2n_renegotiate_request_cb.
96121
*
97122
* The application MUST repeat any connection-specific setup after calling this method. This method
98123
* cannot distinguish between internal connection state and configuration state set by the application,
@@ -130,6 +155,10 @@ S2N_API int s2n_renegotiate_wipe(struct s2n_connection *conn);
130155
* copy the data to `app_data_buf`, and set `app_data_size` to the size of the data.
131156
* The application should handle the data in `app_data_buf` before calling s2n_renegotiate again.
132157
*
158+
* This method cannot be called from inside s2n_renegotiate_request_cb. The receive
159+
* call that triggered s2n_renegotiate_request_cb must complete before either
160+
* s2n_renegotiate_wipe or s2n_renegotiate can be called.
161+
*
133162
* @note s2n_renegotiate_wipe MUST be called before this method.
134163
* @note Calling this method on a server connection will fail. s2n-tls servers do not support renegotiation.
135164
*

bindings/rust/s2n-tls-tokio/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,7 @@ where
171171
&mut self.stream
172172
}
173173

174-
async fn open(mut conn: C, stream: S) -> Result<Self, Error> {
175-
conn.as_mut().set_blinding(Blinding::SelfService)?;
174+
async fn open(conn: C, stream: S) -> Result<Self, Error> {
176175
let mut tls = TlsStream {
177176
conn,
178177
stream,
@@ -203,6 +202,7 @@ where
203202
self.as_mut().set_receive_context(context)?;
204203
self.as_mut().set_send_context(context)?;
205204
self.as_mut().set_waker(Some(ctx.waker()))?;
205+
self.as_mut().set_blinding(Blinding::SelfService)?;
206206

207207
let result = action(Pin::new(self));
208208

bindings/rust/s2n-tls/src/connection.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#![allow(clippy::missing_safety_doc)] // TODO add safety docs
55

6+
#[cfg(feature = "unstable-renegotiate")]
7+
use crate::renegotiate::RenegotiateState;
68
use crate::{
79
callbacks::*,
810
cert_chain::CertificateChain,
@@ -580,23 +582,33 @@ impl Connection {
580582
/// [negotiate](`Self::poll_negotiate`) has succeeded.
581583
///
582584
/// Returns the number of bytes written, and may indicate a partial write.
585+
#[cfg(not(feature = "unstable-renegotiate"))]
583586
pub fn poll_send(&mut self, buf: &[u8]) -> Poll<Result<usize, Error>> {
584587
let mut blocked = s2n_blocked_status::NOT_BLOCKED;
585588
let buf_len: isize = buf.len().try_into().map_err(|_| Error::INVALID_INPUT)?;
586589
let buf_ptr = buf.as_ptr() as *const ::libc::c_void;
587590
unsafe { s2n_send(self.connection.as_ptr(), buf_ptr, buf_len, &mut blocked).into_poll() }
588591
}
589592

593+
#[cfg(not(feature = "unstable-renegotiate"))]
594+
pub(crate) fn poll_recv_raw(
595+
&mut self,
596+
buf_ptr: *mut ::libc::c_void,
597+
buf_len: isize,
598+
) -> Poll<Result<usize, Error>> {
599+
let mut blocked = s2n_blocked_status::NOT_BLOCKED;
600+
unsafe { s2n_recv(self.connection.as_ptr(), buf_ptr, buf_len, &mut blocked).into_poll() }
601+
}
602+
590603
/// Reads and decrypts data from a connection where
591604
/// [negotiate](`Self::poll_negotiate`) has succeeded.
592605
///
593606
/// Returns the number of bytes read, and may indicate a partial read.
594607
/// 0 bytes returned indicates EOF due to connection closure.
595608
pub fn poll_recv(&mut self, buf: &mut [u8]) -> Poll<Result<usize, Error>> {
596-
let mut blocked = s2n_blocked_status::NOT_BLOCKED;
597609
let buf_len: isize = buf.len().try_into().map_err(|_| Error::INVALID_INPUT)?;
598610
let buf_ptr = buf.as_ptr() as *mut ::libc::c_void;
599-
unsafe { s2n_recv(self.connection.as_ptr(), buf_ptr, buf_len, &mut blocked).into_poll() }
611+
self.poll_recv_raw(buf_ptr, buf_len)
600612
}
601613

602614
/// Reads and decrypts data from a connection where
@@ -614,7 +626,6 @@ impl Connection {
614626
&mut self,
615627
buf: &mut [MaybeUninit<u8>],
616628
) -> Poll<Result<usize, Error>> {
617-
let mut blocked = s2n_blocked_status::NOT_BLOCKED;
618629
let buf_len: isize = buf.len().try_into().map_err(|_| Error::INVALID_INPUT)?;
619630
let buf_ptr = buf.as_ptr() as *mut ::libc::c_void;
620631

@@ -623,7 +634,7 @@ impl Connection {
623634
// 2. if s2n_recv returns `+n`, it guarantees that the first
624635
// `n` bytes of `buf` have been initialized, which allows this
625636
// function to return `Ok(n)`
626-
unsafe { s2n_recv(self.connection.as_ptr(), buf_ptr, buf_len, &mut blocked).into_poll() }
637+
self.poll_recv_raw(buf_ptr, buf_len)
627638
}
628639

629640
/// Attempts to flush any data previously buffered by a call to [send](`Self::poll_send`).
@@ -1168,6 +1179,16 @@ impl Connection {
11681179
Some(app_context) => app_context.downcast_mut::<T>(),
11691180
}
11701181
}
1182+
1183+
#[cfg(feature = "unstable-renegotiate")]
1184+
pub(crate) fn renegotiate_state_mut(&mut self) -> &mut RenegotiateState {
1185+
&mut self.context_mut().renegotiate_state
1186+
}
1187+
1188+
#[cfg(feature = "unstable-renegotiate")]
1189+
pub(crate) fn renegotiate_state(&self) -> &RenegotiateState {
1190+
&self.context().renegotiate_state
1191+
}
11711192
}
11721193

11731194
struct Context {
@@ -1177,6 +1198,8 @@ struct Context {
11771198
verify_host_callback: Option<Box<dyn VerifyHostNameCallback>>,
11781199
connection_initialized: bool,
11791200
app_context: Option<Box<dyn Any + Send + Sync>>,
1201+
#[cfg(feature = "unstable-renegotiate")]
1202+
pub(crate) renegotiate_state: RenegotiateState,
11801203
}
11811204

11821205
impl Context {
@@ -1188,6 +1211,8 @@ impl Context {
11881211
verify_host_callback: None,
11891212
connection_initialized: false,
11901213
app_context: None,
1214+
#[cfg(feature = "unstable-renegotiate")]
1215+
renegotiate_state: RenegotiateState::default(),
11911216
}
11921217
}
11931218
}

0 commit comments

Comments
 (0)