Giter Site home page Giter Site logo

schannel-rs's Introduction

schannel-rs .github/workflows/ci.yaml

Documentation

Rust bindings to the Windows SChannel APIs providing TLS client and server functionality.

schannel-rs's People

Contributors

alexcrichton avatar ancwrd1 avatar arlosi avatar bbigras avatar bmarcaur avatar brson avatar cristianbdg avatar cyang1 avatar douglasdwyer avatar e00e avatar expyron avatar frewsxcv avatar goirad avatar inejge avatar jethrogb avatar jonhoo avatar kapji avatar kayabanerve avatar lazka avatar mcgoo avatar mdonoughe avatar mozgiii avatar sergejjurecko avatar sfackler avatar sstecko avatar stanislav-tkach avatar steffengy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

schannel-rs's Issues

Question: how do I use PrivateKey?

I am trying to satisfy the Rustls::ServerConfig builder pattern.

Using the CertStore, I am able to extract the cert that I need with a specific fingerprint, but how do I actually utilize the private key to satisfy fn set_single_cert()?


        let server_opt = find_cert(&rest_fingerprint);
        let cert_context = match server_opt {
            Some(context) => context,
            None => {
                return Err(format!{"cert not found {}", rest_fingerprint});
            }
        };
        let cert_friendly = cert_context.friendly_name().unwrap_or("<unknown>".to_string());

        let mut sc = ServerConfig::new(NoClientAuth::new());

        loop {
            let cert = match cert_context.to_pem() {
                Ok(pem) => rustls::Certificate(pem.into_bytes()),
                Err(e) => {
                    warn!{"unable to convert {} to pem. error {:?}", &cert_friendly, e};
                    break;
                }
            };
            let der = cert_context.to_der();
            let pkey = match cert_context.private_key().acquire() {
                Ok(key) => key,
                Err(e) => {
                    error!("unable to acquire private key for cert {}, error {:?}", &cert_friendly, e);
                    break;
                }
            };
            let key = match pkey {
                PrivateKey::NcryptKey(key) => key,
                PrivateKey::CryptProv(_prov) => {
                    error!("unable to acquire private key for cert {}", &cert_friendly);
                    break;
                }
            };

            warn!{"private key found for {:?}", cert};
            //  the following doesn't work, I believe that I need to somehow use key above
            let private_key = rustls::PrivateKey(der.to_vec());
            match sc.set_single_cert(vec!{cert}, private_key) {
                Ok(()) => {

                }
                Err(e) => {
                    error!{"couldn't set cert {}, error {:?}", &cert_friendly, e};
                }
            }
            break;
        }

Example importing key into cert store

I tried following the example here, but havent been able to get any keys imported into the cert store successfully. I instead get this, error saying "ASN1 bad tag value met."

Needs to handle SEC_I_INCOMPLETE_CREDENTIALS from InitializeSecurityContext

match status {

The server might request credentials (i.e. a client-side certificate for authentication), but does not necessarily actually require it (SEC_E_INCOMPLETE_CREDENTIALS at a later time, note the I vs. E). When getting SEC_I_INCOMPLETE_CREDENTIALS, InitializeSecurityContext() just has to be called again like the first time (without any input) and will produce new output then.

Alternatively one can create a new credentials handle with any client certificates before doing the above.

TlsStream server is busy-waiting if TLS handshake is delayed by client

I tried to build a async wrapper around schannel::tls_stream::Builder to build a TLS server which can make use of certificates from the Windows CertStore. The wrapper works when used as expected. However while testing I encountered a problem that the thread/task handling the MidHandshakeTlsStream would block if a TCP connection is opened but no handshake started.

While debugging I found out that the the call to accept would end up in a busy-loop resulting the process using as much CPU as possible if a TCP connection is opened but no handshake takes place. (For example opening a telnet connection to the listening socket.) This even happens if I ditch my async wrapper and use the schannel in a synchronous fashion.

MidHandshakeTlsStream::handshake calls TlsStream::initialize which in turn calls TlsStream::step_initialize in a enless loop. As there is no data ready for a handshake yet the call to Identity::AcceptSecurityContext returns a status of Foundation::SEC_E_INCOMPLETE_MESSAGE which ends up with needs_read to being set to 0. With needs_read as 0 there is of cours no need to return a WouldBlock error so Identity::AcceptSecurityContext is called again right away in a endless loop.

If I changed the Foundation::SEC_E_INCOMPLETE_MESSAGE arm to always set needs_read to at least one, this busy-loop was broken and the process would not hog the cpu but wait for data being received in the sync case or yield to other tasks in the async case.

While I think this is a bug chances are that I am just to inexperienced with rust and schannel and just made a fool out of myself.

Support SCH_CRED_NO_DEFAULT_CREDS flag on AcquireCredentialsHandleA

Hi,
I'm working on add windows SSL support for MySQL by rust-mysql-simple. And was blocked for the TLSv1.1 Server hello done and Client send cert. Without SCH_CRED_NO_DEFAULT_CREDS flag on AcquireCredentialsHandleA, schannel will pick a cert from current_user/local_machine cert store and send it. That is not expected and server will response BadHandshake. Must set the SCH_CRED_NO_DEFAULT_CREDS on the AcquireCredentialsHandleA can solve this problem.

Workaround patch is that in https://github.com/steffengy/schannel-rs/blob/master/src/schannel_cred.rs#L196
Change code to

cred_data.dwFlags = winapi::SCH_USE_STRONG_CRYPTO | winapi::SCH_CRED_NO_DEFAULT_CREDS;

Can you provide a way to set the SCH_CRED_NO_DEFAULT_CREDS flag on the schannel_cred::Builder ?

Support for Mutual TLS

Context:

  • Client and server each initialize SchannelCred with schannel_cred::SchannelCred::builder().cert(certificate). Client and server each have their own cert.

Observed behavior:

  • the TlsStream on the client-side shows: client_tls_stream.peer_certificate() returns the server certificate, as expected
  • the TlsStream on the server-side has: server_tls_stream.peer_certificate() returns an Err value: Os { code: -2146893042, kind: Uncategorized, message: "No credentials are available in the security package"

Expected behavior:

  • The server_tls_stream.peer_certificate() should show the client certificate and the TLS connection is authenticated on both sides.

Strange handshake errors when running multiple TLS servers on the same machine (through rust-native-tls)

I have a project where I'm generating a self-signed certs with rcgen and then using that cert with rust-native-tls & hyper. I've run into some strange problems when testing that I've isolated into a repro. I believe this is an schannel issue, because I can only reproduce it on Windows, and the handshake error is coming from this crate.

To reproduce (the order of operations here is very important):

  1. Clone https://github.com/jfaust/rust-self-signed-native-tls
  2. In one shell, run cargo run --example server 12345
  3. In another shell, run curl -v --insecure https://localhost:12345/ - it should succeed. This is just to confirm that everything seems to be working. Running that command over and over works just fine.
  4. In another shell, run cargo run --example server 12346
  5. Now run that same curl command again (curl -v --insecure https://localhost:12345/). I get this error:
curl -v --insecure https://localhost:12345/
*   Trying 127.0.0.1:12345...
* Connected to localhost (127.0.0.1) port 12345 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
* [CONN-0-0][CF-SSL] TLSv1.0 (OUT), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Client hello (1):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Server hello (2):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS handshake, Certificate (11):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Unknown (21):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS alert, decrypt error (563):
* OpenSSL/3.0.8: error:02000086:rsa routines::last octet invalid
* Closing connection 0
curl: (35) OpenSSL/3.0.8: error:02000086:rsa routines::last octet invalid

Sometimes it's last octet invalid, sometimes first octet invalid, sometimes data too large for modulus.
6. If you restart the first server, the curl command will work again. However, if you now run it against the second server (port 12346), that one no longer works (you can test it before step (5) to confirm it worked initially).

It seems like there is some kind of cross-process corruption happening somehow, and I am baffled as to how that would be the case. As far as I can tell, the two processes are completely disconnected from each other - they're bound to different ports, they use different self-signed certs (generated at process startup), and they're not communicating with each other in any way.

Any thoughts on what could be happening here?

Should errors in shutdown be ignored?

Over at tokio-tls we've got a smoke test which just tests a simple TLS handshake and sending some bytes from a client to a server, but notably limits I/O reads/writes to only progress one byte at a time. This is intended to stress retry logic and ensure that at least most paths are covered.

Previously this test passed, but recently a change was made to properly issue a shutdown operation from the server side when it's done writing (the test just writes a bunch of bytes from the server to the client). This unfortunately has started making the test fail with schannel!

It looks like schannel is acknowledging a shutdown and attempting to run its own, but then during that function a WSAECONNRESET is received during a TCP write (b/c the other end has dropped at that point).

I'm curious, have you seen this before? Are you familiar with behavior like this? I'm not sure if this is a bug in the test (e.g. requiring a call here or there) or if it's a case where the error should be ignored. The test passes on OSX/Linux FWIW, but Windows (schannel) may just be exposing a surprising edge case.

In any case, thoughts would be welcome!

Can read up to 5 bytes after every complete message

needs_read: 1,

The TLS chunks always start with a 5 byte header (1 byte content type, 2 bytes protocol version, 2 bytes length). At that point the length of the whole packet is known and SEC_E_INCOMPLETE_MESSAGE will be returned, usually providing the whole size in SECBUFFER_MISSING then (sometimes not, and byte by byte has to be read).

By starting with 5 bytes, performance should be improved a little bit.

Question: why NCRYPT_KEY_HANDLE is private?

I'm trying to export the certificate private key using the NCryptExportKey function but the needed key handle is private in the NcryptKey struct. Why? Does exist other ways how to obtain a private key handle?

Panic at ContextBuffer::deref

When initializing ContextBuffer with a null pvBuffer in tls_stream.rs, it triggers a panic at the from_raw_parts.
2024-05-29 155131
2024-05-29 155207
2024-05-29 155307
2024-05-29 154021

Can't connect to cert-requesting servers without client cert

When using this crate to connect to a TLS server that requests a client certificate, and no client certificate was configured by the program, the connection fails with the error SEC_I_INCOMPLETE_CREDENTIALS.

extern crate schannel;

use std::net::TcpStream;
use std::io::{self, Write};

use schannel::schannel_cred::{SchannelCred, Direction};
use schannel::tls_stream::{Builder as TlsStreamBuilder};

fn main() {
	let s = TcpStream::connect(std::env::args().nth(1).unwrap()).unwrap();
    let mut s = TlsStreamBuilder::new().connect(SchannelCred::builder().acquire(Direction::Outbound).unwrap(), s).unwrap();
	s.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
	io::copy(&mut s, &mut io::stdout()).unwrap();
}
$ cargo run auth.mit.edu:444
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Failure(Error { repr: Os { code: 590624, message: "The credentials supplied were not complete, and could not be verified. Additional information can be returned from the context." } })', C:\bot\slave\stable-dist-rustc-win-msvc-64\build\src\libcore\result.rs:868
note: Run with `RUST_BACKTRACE=1` for a backtrace.

I'm actively investigating solutions.

Soundness: CertStore and CertContext `Send` and `Clone` are mutually exclusive

CertStore and CertContext are manually marked Send right now. This would not be a problem, but Clone enables you to get a second copy of either of these, which can then in turn be used to provide mutable access to the underlying object.

Windows has no documentation on the thread safety of its crypto APIs, but there is reason to believe that having multiple mutable accessors is not thread safe. See https://bugs.chromium.org/p/chromium/issues/detail?id=47648 for discussion on why.

One or the other of these needs to be changed. The Clone impl for CertStore can be fixed by opening a new store and manually copying all the certs to it. I can't think of an obvious solution for CertContext beyond making it !Send.

The CertContext::cert_store method suffers from a similar problem. I think we can solve this by making CertStore #[repr(transparent)] and then having CertContext::cert_store return an &'a CertStore. Alternatively we just clone the CertStore.

This will have major effects on upstream, some of whom require these to be Send+Sync. To remedy this, a utility function that copies a CertContext into a new CertStore can be introduced. Upstreams can then use CertStore::certs().next() when they need access to the CertContext within a thread. We could also have a helper type to make this pattern easier to use.

Note also that we will need to audit all uses of both of these to ensure that any Windows function which is not thread safe is called with a mutable reference. Right off the bat I am not sure if acquiring a private key is thread safe, so that might need unique access.

Something is wrong with TlsStream's Write implementation leading to data corruption

I noticed data corruption when sending data over https with reqwest and started investigating: seanmonstar/reqwest#164

First I saw that sometimes TlsStream's encrypt method would never receive the last byte of my input.
Then I looked at TlsStream's Write implementation at https://github.com/steffengy/schannel-rs/blob/master/src/tls_stream.rs#L798 and noticed the following behavior:

  • write is called with a buffer of length 106, this buffer has at its end the second to last byte of my input
  • write enters the if and calls encrypt on this buffer
  • write calls write_out and forwards the resulting WouldBlock
  • write is called twice with a buffer of length107, now also containing the very last byte of my input at its end
  • both times write does not call encrypt but calls write_out and forwards the resulting WouldBlock
  • write is called a third time again with that buffer, it again does not call encrypt but callswrite_outsuccessfully (which returned Ok(135)) and returns Ok(107)

I dont know what exactly is going wrong yet or how to fix it but this definitely looks like a bug inschannel-rs to me. TlsStream is receiving a buffer to write and responds that it has written the buffer while not having ever encrypted or sent the last byte of that buffer.

Win81/Server2012R2: sporadic build failures: SEC_E_BUFFER TOO_SMALL or (less common) SEC_E_MESSAGE_ALTERED

as in: https://ci.appveyor.com/project/steffengy/schannel-rs/build/1.0.33/job/t6e7xfcvc507tksn

The SEC_E_BUFFER TOO_SMALL seems to be the issue mentioned here: Waffle/waffle#128 (comment)
(confirmed by wireshark capture below)

related MS-bug report

The SEC_E_MESSAGE_ALTERED likely is another issue.

  • Running the tests on Win8.1 or Server 2012 R2 (ONLY these versions) results in sporadic
    build failures with the error codes mentioned in the title
:a
cargo test validation_failure_is_permanent && goto a
pause
  • Running them on Win7, Server 2008, Win8, Server 2012, Win10, Server 2016 (Preview)
    does not result in sporadic failures.
  • Removing
    TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 and TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 from the schannel SSL cipher suites order, results in the disappearance of this bug.

Working Wireshark Capture + Broken SEC_E_MESSAGE_ALTERED

Wireshark Capture of Broken SEC_E_BUFFER_TOO_SMALL

Interface changes to support RFC 5077 TLS session tickets

It looks like SChannel natively advertises support for the SessionTicket extension in its ClientHello, but scopes its stored tickets in some way to the CredHandle used to build the session.

As currently written, tls_stream::Builder consumes the given SchannelCred, so it's not possible to use this crate in a way that allows for RFC 5077.

pub fn connect<S>(&mut self,
cred: SchannelCred,
stream: S)
-> Result<TlsStream<S>, HandshakeError<S>>

Would it be possible to update the interface to allow SchannelCred to be shared/refcounted? At a glance through the curl's usage of schannel, it looks like the Windows APIs that use CredHandle objects are most likely thread-safe.

Ref: https://github.com/curl/curl/blob/0e06c1637b30800d41636dcd02f5becf3f6664c1/lib/vtls/schannel.c#L794

Panics on Rust 1.78: "unsafe precondition(s) violated: slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`"

I encountered this panic making a request using reqwest with the Tokio runtime. This seems to be an UB caughted by the new precondition assertions.

Here's the backtrace:

thread 'main' panicked at library\core\src\panicking.rs:156:5:
unsafe precondition(s) violated: slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\std\src\panicking.rs:645
   1: core::panicking::panic_nounwind_fmt::runtime
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\core\src\panicking.rs:110
   2: core::panicking::panic_nounwind_fmt
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\core\src\panicking.rs:123
   3: core::panicking::panic_nounwind
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\core\src\panicking.rs:156
   4: core::slice::raw::from_raw_parts::precondition_check
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\intrinsics.rs:2799
   5: core::slice::raw::from_raw_parts<u8>
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\slice\raw.rs:98
   6: schannel::context_buffer::impl$1::deref
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\schannel-0.1.20\src\context_buffer.rs:20
   7: schannel::tls_stream::TlsStream<tokio_native_tls::AllowStd<tokio::net::tcp::stream::TcpStream> >::step_initialize<tokio_native_tls::AllowStd<tokio::net::tcp::stream::TcpStream> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\schannel-0.1.20\src\tls_stream.rs:579
   8: schannel::tls_stream::TlsStream<tokio_native_tls::AllowStd<tokio::net::tcp::stream::TcpStream> >::initialize<tokio_native_tls::AllowStd<tokio::net::tcp::stream::TcpStream> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\schannel-0.1.20\src\tls_stream.rs:655
   9: schannel::tls_stream::MidHandshakeTlsStream<tokio_native_tls::AllowStd<tokio::net::tcp::stream::TcpStream> >::handshake<tokio_native_tls::AllowStd<tokio::net::tcp::stream::TcpStream> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\schannel-0.1.20\src\tls_stream.rs:972
  10: native_tls::imp::MidHandshakeTlsStream<tokio_native_tls::AllowStd<tokio::net::tcp::stream::TcpStream> >::handshake<tokio_native_tls::AllowStd<tokio::net::tcp::stream::TcpStream> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\native-tls-0.2.11\src\imp\schannel.rs:206
  11: native_tls::MidHandshakeTlsStream<tokio_native_tls::AllowStd<tokio::net::tcp::stream::TcpStream> >::handshake<tokio_native_tls::AllowStd<tokio::net::tcp::stream::TcpStream> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\native-tls-0.2.11\src\lib.rs:255
  12: tokio_native_tls::impl$18::poll<tokio::net::tcp::stream::TcpStream>
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-native-tls-0.3.1\src\lib.rs:366
  13: tokio_native_tls::handshake::async_fn$0<tokio_native_tls::impl$12::connect::async_fn$0::closure_env$0<tokio::net::tcp::stream::TcpStream>,tokio::net::tcp::stream::TcpStream>
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-native-tls-0.3.1\src\lib.rs:255
  14: tokio_native_tls::impl$12::connect::async_fn$0<tokio::net::tcp::stream::TcpStream>
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-native-tls-0.3.1\src\lib.rs:311
  15: hyper_tls::client::impl$5::call::async_block$1<hyper::client::connect::http::HttpConnector<reqwest::dns::resolve::DynResolver> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\hyper-tls-0.5.0\src\client.rs:138  
  16: core::future::future::impl$1::poll<alloc::boxed::Box<dyn$<core::future::future::Future<assoc$<Output,enum2$<core::result::Result<enum2$<hyper_tls::stream::MaybeHttpsStream<tokio::net::tcp::stream::TcpStream> >,alloc::boxed::Box<dyn$<core::error::Error,cor
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\future\future.rs:123
  17: hyper_tls::client::impl$6::poll<tokio::net::tcp::stream::TcpStream>
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\hyper-tls-0.5.0\src\client.rs:162  
  18: reqwest::connect::impl$0::connect_with_maybe_proxy::async_fn$0
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\reqwest-0.11.22\src\connect.rs:258 
  19: reqwest::connect::with_timeout::async_fn$0<reqwest::connect::Conn,enum2$<reqwest::connect::impl$0::connect_with_maybe_proxy::async_fn_env$0> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\reqwest-0.11.22\src\connect.rs:432 
  20: core::future::future::impl$1::poll<alloc::boxed::Box<dyn$<core::future::future::Future<assoc$<Output,enum2$<core::result::Result<reqwest::connect::Conn,alloc::boxed::Box<dyn$<core::error::Error,core::marker::Send,core::marker::Sync>,alloc::alloc::Global>
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\future\future.rs:123
  21: hyper::service::oneshot::impl$0::poll<reqwest::connect::Connector,http::uri::Uri>
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\hyper-0.14.26\src\service\oneshot.rs:60
  22: futures_core::future::impl$2::try_poll<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,reqwest::connect::Conn,alloc::boxed::Box<dyn$<core::error::Error,core::marker::Send,core::marker::Sync>,alloc::alloc::Global> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-core-0.3.28\src\future.rs:82
  23: futures_util::future::try_future::into_future::impl$2::poll<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\future\try_future\into_future.rs:34
  24: futures_util::future::future::map::impl$2::poll<futures_util::future::try_future::into_future::IntoFuture<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri> >,futures_util::fns::MapErrFn<hyper::error::Error (*)(alloc::boxed::Box<
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\future\future\map.rs:55
  25: futures_util::future::future::impl$15::poll<futures_util::future::try_future::into_future::IntoFuture<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri> >,futures_util::fns::MapErrFn<hyper::error::Error (*)(alloc::boxed::Box<dyn$
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\lib.rs:91  
  26: futures_util::future::try_future::impl$61::poll<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::boxed::Box<dyn$<core::error::Error,core::marker::Send,core::marker::Sync>,alloc::alloc::Global>)>
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\lib.rs:91  
  27: futures_core::future::impl$2::try_poll<futures_util::future::try_future::MapErr<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::boxed::Box<dyn$<core::error::Error,core::marker::Send,core::marker:
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-core-0.3.28\src\future.rs:82
  28: futures_util::future::try_future::into_future::impl$2::poll<futures_util::future::try_future::MapErr<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::boxed::Box<dyn$<core::error::Error,core::marke
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\future\try_future\into_future.rs:34
  29: futures_util::future::future::map::impl$2::poll<futures_util::future::try_future::into_future::IntoFuture<futures_util::future::try_future::MapErr<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\future\future\map.rs:55
  30: futures_util::future::future::impl$15::poll<futures_util::future::try_future::into_future::IntoFuture<futures_util::future::try_future::MapErr<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::boxe
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\lib.rs:91  
  31: futures_util::future::try_future::impl$56::poll<futures_util::future::try_future::MapErr<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::boxed::Box<dyn$<core::error::Error,core::marker::Send,core
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\lib.rs:91  
  32: futures_core::future::impl$2::try_poll<futures_util::future::try_future::MapOk<futures_util::future::try_future::MapErr<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::boxed::Box<dyn$<core::error
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-core-0.3.28\src\future.rs:82
  33: futures_util::future::try_future::try_flatten::impl$2::poll<futures_util::future::try_future::MapOk<futures_util::future::try_future::MapErr<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::boxed:
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\future\try_future\try_flatten.rs:49
  34: futures_util::future::try_future::impl$4::poll<futures_util::future::try_future::MapOk<futures_util::future::try_future::MapErr<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::boxed::Box<dyn$<cor
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\lib.rs:91  
  35: futures_util::future::try_future::impl$26::poll<futures_util::future::try_future::MapErr<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::boxed::Box<dyn$<core::error::Error,core::marker::Send,core
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\lib.rs:91  
  36: futures_util::future::either::impl$4::poll<futures_util::future::try_future::AndThen<futures_util::future::try_future::MapErr<hyper::service::oneshot::Oneshot<reqwest::connect::Connector,http::uri::Uri>,hyper::error::Error (*)(alloc::boxed::Box<dyn$<core:
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\future\either.rs:109
  37: hyper::common::lazy::impl$1::poll<hyper::client::client::impl$3::connect_to::closure_env$0<reqwest::connect::Connector,reqwest::async_impl::body::ImplStream>,enum2$<futures_util::future::either::Either<futures_util::future::try_future::AndThen<futures_uti
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\hyper-0.14.26\src\common\lazy.rs:62
  38: futures_util::future::future::FutureExt::poll_unpin<hyper::common::lazy::Lazy<hyper::client::client::impl$3::connect_to::closure_env$0<reqwest::connect::Connector,reqwest::async_impl::body::ImplStream>,enum2$<futures_util::future::either::Either<futures_u
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\future\future\mod.rs:562
  39: futures_util::future::select::impl$1::poll<hyper::client::pool::Checkout<hyper::client::client::PoolClient<reqwest::async_impl::body::ImplStream> >,hyper::common::lazy::Lazy<hyper::client::client::impl$3::connect_to::closure_env$0<reqwest::connect::Connec
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\futures-util-0.3.28\src\future\select.rs:118
  40: hyper::client::client::impl$3::connection_for::async_fn$0<reqwest::connect::Connector,reqwest::async_impl::body::ImplStream>
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\hyper-0.14.26\src\client\client.rs:368
  41: hyper::client::client::impl$3::send_request::async_fn$0<reqwest::connect::Connector,reqwest::async_impl::body::ImplStream>
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\hyper-0.14.26\src\client\client.rs:236
  42: hyper::client::client::impl$3::retryably_send_request::async_fn$0<reqwest::connect::Connector,reqwest::async_impl::body::ImplStream>
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\hyper-0.14.26\src\client\client.rs:206
  43: hyper::client::client::impl$10::poll
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\hyper-0.14.26\src\client\client.rs:623
  44: reqwest::async_impl::client::impl$13::poll
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\reqwest-0.11.22\src\async_impl\client.rs:2263
  45: reqwest::async_impl::client::impl$12::poll
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\reqwest-0.11.22\src\async_impl\client.rs:2241
  46: cli::auth::get_github_user::async_fn$0
             at .\src\auth.rs:128
  47: cli::auth::impl$2::is_expired::async_fn$0
             at .\src\auth.rs:143
  48: cli::auth::impl$8::maybe_refresh_token::async_fn$0
             at .\src\auth.rs:578
  49: cli::auth::impl$8::get_credential::async_fn$0
             at .\src\auth.rs:508
  50: cli::auth::impl$8::get_tunnel_authentication::async_fn$0
             at .\src\auth.rs:447
  51: cli::auth::impl$18::get_authorization::async_block$0
             at .\src\auth.rs:810
  52: core::future::future::impl$1::poll<alloc::boxed::Box<dyn$<core::future::future::Future<assoc$<Output,enum2$<core::result::Result<enum2$<tunnels::management::authorization::Authorization>,enum2$<tunnels::management::errors::HttpError> > > > >,core::marker:
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\future\future.rs:123
  53: tunnels::management::http_client::impl$0::make_request::async_fn$0
             at C:\Users\conno\.cargo\git\checkouts\dev-tunnels-99bcc8e49562bf97\8cae9b2\rs\src\management\http_client.rs:474
  54: tunnels::management::http_client::impl$0::make_tunnel_request::async_fn$0
             at C:\Users\conno\.cargo\git\checkouts\dev-tunnels-99bcc8e49562bf97\8cae9b2\rs\src\management\http_client.rs:433
  55: tunnels::management::http_client::impl$0::get_tunnel::async_fn$0
             at C:\Users\conno\.cargo\git\checkouts\dev-tunnels-99bcc8e49562bf97\8cae9b2\rs\src\management\http_client.rs:92
  56: opentelemetry_api::trace::context::impl$4::poll<enum2$<tunnels::management::http_client::impl$0::get_tunnel::async_fn_env$0> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\opentelemetry_api-0.19.0\src\trace\context.rs:373
  57: cli::tunnels::dev_tunnels::impl$4::get_or_create_tunnel::async_fn$0
             at .\src\tunnels\dev_tunnels.rs:443
  58: cli::tunnels::dev_tunnels::impl$4::start_new_launcher_tunnel::async_fn$0
             at .\src\tunnels\dev_tunnels.rs:484
  59: cli::commands::tunnels::serve_with_csa::async_fn$0::closure$3
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.28.2\src\macros\select.rs:524
  60: tokio::future::poll_fn::impl$1::poll<enum2$<cli::commands::tunnels::serve_with_csa::async_fn$0::__tokio_select_util::Out<enum2$<core::result::Result<cli::tunnels::dev_tunnels::ActiveTunnel,enum2$<cli::util::errors::AnyError> > >,enum2$<core::result::Resul
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.28.2\src\future\poll_fn.rs:58
  61: cli::commands::tunnels::serve_with_csa::async_fn$0
             at .\src\commands\tunnels.rs:602
  62: cli::commands::tunnels::serve::async_fn$0
             at .\src\commands\tunnels.rs:419
  63: code::main::async_block$0
             at .\src\bin\code\main.rs:124
  64: tokio::runtime::park::impl$4::block_on::closure$0<enum2$<code::main::async_block_env$0> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.28.2\src\runtime\park.rs:283
  65: tokio::runtime::coop::with_budget
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.28.2\src\runtime\coop.rs:107
  66: tokio::runtime::coop::budget
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.28.2\src\runtime\coop.rs:73
  67: tokio::runtime::park::CachedParkThread::block_on<enum2$<code::main::async_block_env$0> >
  69: tokio::runtime::scheduler::multi_thread::MultiThread::block_on<enum2$<code::main::async_block_env$0> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.28.2\src\runtime\scheduler\multi_thread\mod.rs:66
  70: tokio::runtime::runtime::Runtime::block_on<enum2$<code::main::async_block_env$0> >
             at C:\Users\conno\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.28.2\src\runtime\runtime.rs:304
  71: code::main
             at .\src\bin\code\main.rs:129
  72: core::ops::function::FnOnce::call_once<enum2$<core::result::Result<tuple$<>,enum2$<core::convert::Infallible> > > (*)(),tuple$<> >    
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\ops\function.rs:250
  73: core::hint::black_box
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\hint.rs:337

TLS negotiation fails when `schannel` (via `native_tls`) tries to connect to an OpenSSL 3 server

I want to use schannel via native-tls in a client-side program to talk to an OpenSSL server. For the most part, this works fine, except when:

  • The client is running on Windows 10 21H2. (I don't have any other version of Windows to try this on, so I don't know if it's version-specific.)
  • The server uses OpenSSL 3. (The problem does not appear if the server uses schannel via native-tls on Windows, or if it uses OpenSSL 1.1.1.)

Under these circumstances, the schannel-via-native-tls-based client slowly allocates up to 4GB of memory, then hangs.

Here's a GitHub repository with a simple test program that demonstrates the problem. To use it:

  1. Make sure the openssl command is available and is OpenSSL 3 (check openssl version). On Windows, I've tried with the FireDaemon binaries, which demonstrate the problem. (The test program works correctly with FireDaemon's OpenSSL 1.1.1 build; it only fails with OpenSSL 3.)
  2. Check out the linked GitHub repository and cd into it.
  3. cargo run --bin openssl_server -- 127.0.0.1:4433 - this generates some certificates and then runs openssl s_server.
  4. Wait for the server to start. It will say ACCEPT when it's ready.
  5. In another terminal, cargo run --bin native_tls_client -- 127.0.0.1:4433 - this starts up a native-tls client that tries to connect to the server and send it an HTTP request. If it succeeds, it writes the response to stdout.

If you do this on Linux (where native-tls instead uses OpenSSL as the backend), it works fine. If you do this on Windows and the server uses OpenSSL 1.1.1 or SChannel, it works fine. If you do this on Windows and the server uses OpenSSL 3, the client hangs.

I noticed that the curl build that comes with Windows, which I think also uses the SChannel API, works fine even if the server is running OpenSSL 3.

I ran this in a debugger and found that, somehow, when schannel::tls_stream::TlsStream::step_initialize calls InitializeSecurityContextW, the latter function sets inbufs[1].cbBuffer to just under u32::MAX (in my debugger one time, it was 4294966277, but it seems to vary). TlsStream::read_in then allocates and initializes that much memory, which on my machine takes a minute or two, and then hangs trying to read that many bytes from the socket.

I previously submitted this issue as sfackler/rust-native-tls#242, but @sfackler tells me the issue is most likely with the schannel crate. What do you think?

TlsStream blocking indefinitely when using client cert

When trying to connect and read from an IIS application that requires a client cert, <TlsStream as BufRead>::fill_buf appears to be blocking indefinitely, waiting for data that never appears.

I've cloned the repo and added some simple logging, which indicates where the blocking occurs...

DEBUG:schannel::tls_stream: Begin TlsStream::initialize
DEBUG:schannel::tls_stream: Writing from 0
DEBUG:tls_test: Reading response
DEBUG:schannel::tls_stream: TlsStream empty. Filling...
DEBUG:schannel::tls_stream: Initializing TlsStream
DEBUG:schannel::tls_stream: Begin TlsStream::initialize
DEBUG:schannel::tls_stream: TlsStream needs read
DEBUG:schannel::tls_stream: begin read_in
DEBUG:schannel::tls_stream:     Reading 2048 bytes...
DEBUG:schannel::tls_stream:     Read 37 bytes
DEBUG:schannel::tls_stream: Decrypting
DEBUG:schannel::tls_stream: TlsStream empty. Filling...
DEBUG:schannel::tls_stream: Initializing TlsStream
DEBUG:schannel::tls_stream: Begin TlsStream::initialize
DEBUG:schannel::tls_stream:     state is State::Inititializing
DEBUG:schannel::tls_stream:     Writing
DEBUG:schannel::tls_stream:     Validating
DEBUG:schannel::tls_stream:     Setting validated to true
DEBUG:schannel::tls_stream:     needs 1 more bytes
DEBUG:schannel::tls_stream: begin read_in
DEBUG:schannel::tls_stream:     Reading 2048 bytes...
<No further data is received at this point>

My test application uses schannel-rs via rust-native-tls and looks like the following (note: I've added a timeout on the TcpStream to prevent blocking in this instance)...

    static HTTP_REQ: &'static [u8] = b"GET /broker.asmx HTTP/1.1\r\n\
                                  Host: [HOSTNAME]\r\n\
                                  \r\n";

    let mut builder = TlsConnector::builder()
        .unwrap();

    builder
        .add_root_certificate(load_server_cert())
        .unwrap()
        .identity(load_client_cert())
        .unwrap()
        ;

    let connector = builder.build()
        .unwrap();

    let stream = TcpStream::connect("[IP & PORT]").unwrap();
    stream.set_read_timeout(Some(Duration::from_secs(5))).unwrap();

    debug!("Connecting TLS");
    let mut stream = connector.connect("[HOSTNAME]", stream).unwrap();

    debug!("Writing request...\r\n{}", str::from_utf8(HTTP_REQ).unwrap());
    stream.write_all(HTTP_REQ)
        .unwrap();

    let mut response = vec![0_u8; 256];
    debug!("Reading response");
    loop {
        match stream.read(&mut response) {
            Ok(n) => print!("{}", String::from_utf8_lossy(&response[..n])),
            Err(e) => {
                println!("{}", e);
                break;
            }
        }
    }

I'm not familiar with the SChannel's operation so forgive the speculation, but it looks like maybe SChannel is waiting on a write to occur before it can read more data.

I'm happy to take direction on how to diagnose further.

Provide access to the underlying Windows handles

Would it make sense for each wrapper around a Windows type to provide an unsafe raw() method for getting the underlying HANDLEs?

For example:

impl CertStore {
  pub unsafe fn raw(&self) -> HCERTSTORE { self.0 }
}

The idea being that it makes interoperability with other windows APIs easier, and gives users an escape hatch if a particular feature they need isn't implemented yet.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.