-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unexpected behavior of client without certificate on mTLS #83
Comments
That does sound surprising -- I would expect that in this scenario the |
Going to assume this is TLS1.3, going by the openssl error message. The TLS1.3 handshake looks like:
(ref https://datatracker.ietf.org/doc/html/rfc8446) The line I marked [1] is the point at which the client reveals to the server whether it has a client certificate. The line I marked [2] is the point at which the handshake is complete from the point of view of the client, and it is 100% free to start writing data. Observe that there is no round-trip to the server that confirms whether the server has accepted the certificate; this is signaled only with an alert, and that is received asynchronously by the client. However, I would expect the client to fail after one RTT with something like |
I created a fork of the repo and added a test to reproduce my scenario: https://github.com/MattesWhite/tokio-rustls/blob/matteswhite/unexpected-mtls-client/tests/mtls-without-client-cert.rs#L93 |
I can reproduce a similar but different misbehaviour with the example in this repo. Server is: ~/src/rustls/examples$ cargo run --bin tlsserver-mio -- --certs ../test-ca/rsa-2048/end.fullchain --key ../test-ca/rsa-2048/end.key --require-auth --auth ../test-ca/rsa-2048/ca.cert -p8443 --verbose http ~/src/tokio-rustls$ cargo run --example client localhost -p 8443 --cafile ~/src/rustls/test-ca/rsa-2048/ca.cert
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/examples/client localhost -p 8443 --cafile /home/jbp/src/rustls/test-ca/rsa-2048/ca.cert` At this point, the server has rejected the connection, but the client is still hanging around waiting on stdin. Then if I type "e\n":
Even more damning is strace output:
^ this is the alert coming in.
But we go to sleep waiting for stdin! More logging:
So the error does make it out, but then apparently tokio blocks main from exiting because it cannot cancel reading from stdin (tokio-rs/tokio#589, tokio-rs/tokio#2318) |
Thanks! I think there is no point in your program where it can receive a post-handshake alert. These two points are before that: and If I add: diff --git a/tests/mtls-without-client-cert.rs b/tests/mtls-without-client-cert.rs
index 1586543..53821ae 100644
--- a/tests/mtls-without-client-cert.rs
+++ b/tests/mtls-without-client-cert.rs
@@ -101,6 +101,7 @@ async fn connect_to_mtls_without_client_cert() {
.unwrap(); // I'd expect to fail the test here
tls.write(b"123").await.unwrap();
+ tls.read(&mut [0u8]).await.unwrap();
let wait_for = store.wait_for(|store| store.len() >= 3);
timeout(Duration::from_millis(500), wait_for)
Then things start working. |
Okay so to check if I understand you correctly:
The thing is that in my case I don't expect any data from the server I only stream data towards it. Accordingly, I expect the |
I would recommend doing this:
|
My solution for now is to do 500ms timeout on an initial The 500ms timeout is somewhat arbitrary as I don't know how long the roundtrip from client to server takes but I deemed it good enough. BTW I also noticed that every other problem with client authentication, e.g. untrusted client certificate, is also only reported via TLS alerts. |
Why not take @ctz's approach as outlined in the his previous comment? Would avoid the need for timeouts. |
Maybe I didn't understand ctz's approach. My problem is that I want to know if client auth was successful before writing any data to give users an error before they can try to send data because all data written before client auth failed is lost somewhere in the stack. Also as I understood ctz's approach is about closing the connection but my problem is about establishing the connection. If I got it wrong I'm sorry maybe you can explain it to me in more detail? |
Nope, you're correct that @ctz's approach relies on unconditionally writing the data. |
That seems reasonable, on the proviso that that server should immediately close the stream (that will make your read complete on success after 1RTT, rather than time out). |
Okay, I have now a working, good-enough solution. Thank you both for your time and effort to help me 👍. |
Hi there,
I'm currently working on establishing mTLS connections using this crate. My problem is that my program may connect to a TLS server that requires client auth and therefore a client certificate or not. Therefore, it is also possible to not provide a client certificate as the targeted TLS server might not require one.
All in all, there can be the case that the TLS server requires a client certificate but the client does not send one. I expected that in this case
TlsConnector::connect()
would fail but instead it connects successfully, immediate writes work as well and only after about 50ms I get an error on sending with:IoFailure(Os { code: 104, kind: ConnectionReset, message: "Connection reset by peer" })
I tried the same scenario with
openssl s_client
and it fails immediately with:40774BE4047F0000:error:0A00045C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required:../ssl/record/rec_layer_s3.c:1586:SSL alert number 116
. So it seems possible to detect the problem without waiting for a write to fail.Is this a bug or intended behavior on your side? How should such a case be handled? And what happened to the first data of the first succeeded write? In wireshark it appears that that data was never written to the wire.
The text was updated successfully, but these errors were encountered: