The difference between rustls and native-tls is that the former is a pure-Rust implementation of the TLS protocol while the latter is just a wrapper communicating with the TLS implementation provided by the system (e.g OpenSSL).
Are these assumptions correct?
Because rustls does not depend on the system it is more portable.
Code using native-tls might not work on some systems missing dependencies.
rustls is more secure then native-tls because it is pure-Rust (memory safe).
rustls is a suitable solution for software which needs to work on different systems out-of-the-box, while native-tls would be preferred when making system-specific software.
rustls is more performant.
rustls often "just works" while native-tls might give portability issues.
And is it also true that when using rustls you have to specify the root certificates manually?
But my assumption that native-tls might be the better option when making system-specific software is false?
I was imagining the case where one might want to have software, but wanted to be able to dynamically change the TLS provider or CA certs by reconfiguring the system without altering the code.
One final question. In rustls we do have to manually provide the CA store, right? rustls doesn't default to e.g Webpki?
So in this code:
// Get root_certs required for TLS.
// Because we use `rustls` we manually load Mozilla's CA roots, this because
// due to `rustls` we don't have access to the system's CA store.
let mut root_cert_store = tokio_rustls::rustls::RootCertStore::empty();
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let config = tokio_rustls::rustls::ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
// Use `tokio_rustls` connector to create a TLS connection.
// In this example we prefer `rustls` for easier portability. You can alternatively use
// `native-tls` if you prefer.
let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config));
let server_name = host
.to_string()
.try_into()
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "Bad DNS name"))?;
It was a long time ago since I tried to use native-tls, but I remember running into a problem where on macos I'd get some error over and over. Searching for this error yielded lots of results (not only related to Rust), but no one had a solution for it. People speculated that it had to do with a CA not being "properly trusted", but no one had any idea how to remedy that. Eventually I found some discussion that this error was unhelpful because it originated from an old (deprecated) API. If one uses the New And Improved(tm) framework, all problems would probably go away and the errors would be much clearer.
Although the idea of using a "native" TLS implementation was (and still is) appealing, I spent a fair amount of time trying to use a custom CA with it and never got it to work. (I should emphasize: I don't really think this is a native-tls issue, as much as it was a macos issue). While I initially wasn't super happy with rustls' API, at the end of the day I got everything to work as I needed pretty quickly.
(rustls is not without its own problems though; you might suddenly not be able to build on Windows because of the dependency aws-lc-sys being weird, for instance).