feat: add s2n_connection_handshake_complete() public API#5906
feat: add s2n_connection_handshake_complete() public API#5906FreezB11 wants to merge 20 commits into
Conversation
Exposes the internal s2n_handshake_is_complete() check as a public API so external integrations can reliably determine when the TLS handshake is fully complete without relying on indirect signals like error codes, blocked statuses, or handshake type strings. Fixes a TLS 1.2 regression where checking handshake_type() for 'NEGOTIATED' caused the handshake loop to exit one iteration too early, leaving the server's Finished message unread. That stray record then arrived with application data and was misrouted through s2n_post_handshake_recv(), triggering S2N_ERR_BAD_MESSAGE. Changes: - api/s2n.h: declare s2n_connection_handshake_complete() with full docs - tls/s2n_handshake_io.c: implement as a null-safe wrapper around s2n_handshake_is_complete() - tests/unit/s2n_handshake_complete_test.c: tests covering NULL safety, fresh connection, TLS 1.2 and TLS 1.3 completion, return value contract - bindings/rust/extended: add safe handshake_complete() wrapper - bindings/rust/standard: remove brittle handshake_done workaround Closes aws#5625 Related: aws#5624, aws#854
|
@kaukabrizvi @jmayclin, hey there, is it possible for review of this PR |
| match res { | ||
| 1 => Ok(true), | ||
| 0 => Ok(false), | ||
| _ => Err(Error::capture()), |
There was a problem hiding this comment.
pub fn handshake_complete(&self) -> Result<bool, Error> {
unsafe {
s2n_connection_handshake_complete(self.connection.as_ptr())
.into_result()
.map(|res| res != 0)
}
}
@kaukabrizvi do you think will work i didnt see the rust implementation correctly, i hope this will work shall i patch this in
There was a problem hiding this comment.
This suggestion looks good, and is in line with other FFI calls in the file. I'd patch it in.
There was a problem hiding this comment.
I have patched that also there was fmt error which is also fixed.
Could you please look at the rust side of code before pushing it to actions
jmayclin
left a comment
There was a problem hiding this comment.
Could you please adopt the PR template in the repo?
| * @returns 1 if the handshake is complete, 0 if still in progress, | ||
| * or -1 on error (e.g. NULL conn). | ||
| */ | ||
| S2N_API extern int s2n_connection_handshake_complete(struct s2n_connection *conn); |
There was a problem hiding this comment.
I'd prefer to return a bool here.
There was a problem hiding this comment.
int was to represent the state, bool can either be 0,1
There was a problem hiding this comment.
I agree that this is functionally correct, but I would argue that returning a bool is more ergonomic and easier for consumers.
I'd also like to make this function infallible. Outside of the null case, it seems odd to make this function failable. And for the null cause I'd just pick a default value to return (e.g. just always return false if connection is null)
Co-authored-by: James Mayclin <maycj@amazon.com>
Co-authored-by: James Mayclin <maycj@amazon.com>
found the issue form the actions report
|
i have update the PR description, My bad i didnt look into the .github folder, i have updated the connection.rs also, |
struct literal needed to be one line
|
yup finally no errorsssss |
| * "Complete" means all handshake messages have been sent and received, | ||
| * including the TLS 1.2 server Finished message. Once this returns 1, | ||
| * the connection is ready for application data. | ||
| * |
There was a problem hiding this comment.
What is the behavior of this function when negotiation fails? I think we'd want the API to return true, negotiation is complete (and it failed). To double check this could you add some assertions in e.g. https://github.com/aws/s2n-tls/blob/main/bindings/rust/standard/integration/src/handshake/handshake_failure_errors.rs?
| @@ -131,10 +131,6 @@ pub struct S2NConnection { | |||
| io: Pin<Box<ViewIO>>, | |||
| connection: Connection, | |||
| // We have to store the result of s2n_negotiate to know when the handshake is complete. | |||
There was a problem hiding this comment.
This comment is stale.
| EXPECT_EQUAL(s2n_connection_handshake_complete(server_conn), 0); | ||
|
|
||
| /* Drive the handshake fully to completion */ | ||
| EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn)); |
There was a problem hiding this comment.
Since you call negotiate end-to-end, the final assertion would also pass against the old handshake_type contains "NEGOTIATED" logic, both sides finish either way. To actually exercise the path the comment and issue describe, you'd want to drive the handshake until the server has sent Finished but the client hasn't read it yet, and assert completion is false there (where the old logic would have returned true). Without that step, this doesn't add coverage beyond handshake_type.
| EXPECT_SUCCESS(s2n_config_free(config)); | ||
| }; | ||
|
|
||
| /* ── TLS 1.3: complete after initial handshake exchange ───────────── |
There was a problem hiding this comment.
Again, this doesn't really test the claimed behavior. The claim is that post-handshake messages should not reset the completion flag but the test does not send any post-handshake messages.
Drive the handshake step-by-step so we can assert that s2n_connection_handshake_complete() returns 0 on the client side after the server has sent its Finished message but before the client has consumed it. The old handshake_type/NEGOTIATED-bit logic would have returned 1 at that point; the new s2n_handshake_is_complete() path must return 0. Without this intermediate assertion the test offered no coverage beyond the existing handshake_type check.
|
the unit test has been corrected. |
|
@jmayclin @kaukabrizvi @bdonlan @glguy could someone help review and push this |
Goal
Add
s2n_connection_handshake_complete()as a public API so external integrationscan reliably determine when the TLS handshake is fully complete, without relying on
indirect signals like error codes, blocked statuses, or handshake type strings.
Why
While refactoring the dynamic record sizing Rust integration test (#5614) to support
both TLS 1.2 and TLS 1.3, the harness's handshake-completion check
(
handshake_type().contains("NEGOTIATED")) caused TLS 1.2 handshakes to exit theloop one iteration too early. The server's final Finished message was left unread,
then arrived together with application-data bytes and was misrouted through
s2n_post_handshake_recv(), triggeringS2N_ERR_BAD_MESSAGE.More broadly, all external integrations currently infer handshake completion from
indirect signals, which can lead to exiting the loop too early, mishandling leftover
TLS 1.2 handshake bytes, and tight coupling to internal s2n-tls state machine details.
How
Expose the existing internal
s2n_handshake_is_complete()function (which checksACTIVE_STATE(conn).writer == 'B') as a null-safe public API. This is the samecheck that drives
s2n_negotiate_impl()'s own loop, making it the authoritativecompletion signal in the codebase.
Analogous to OpenSSL's
SSL_is_init_finishedand Rustls'is_handshaking.Callouts
The Rust binding uses
.into_result()rather than the privateError::capture().Testing
All 281 unit tests pass including the new
s2n_handshake_complete_test, which coversNULL safety, fresh connection, TLS 1.2, TLS 1.3, and the return value contract.
Related
Closes #5625
Related: #5624, #854
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.