Use `tokio-tungstenite` with `rustls` instead of `native_tls` for secure websockets

Hi, I'm implementing the client side of a web socket connection with tokio-tungstenite and I got it working with the native_tls create but from what I read online a lot of people seem to migrate to rustls. Therefore I wanted to give it a try but I couldn't get it to work.

This is the code for my web socket client using native_tls

use tokio_tungstenite::{Connector, connect_async_tls_with_config};

#[tokio::main]
async fn main() {
    let cert_file = fs::read("certs/localhost.crt").unwrap();
    let cert = native_tls::Certificate::from_pem(&cert_file).unwrap();
    let tls_connector = native_tls::TlsConnector::builder()
        .add_root_certificate(cert)
        .build()
        .unwrap();

    let connector = Connector::NativeTls(tls_connector);

    // Connect to the web socket
    let url = Url::parse("wss://localhost:3000/ws").unwrap();
    let (ws_stream, _) = connect_async_tls_with_config(
        url, None, Some(connector)
    ).await.unwrap();
}

This works perfectly fine.
The following code is what I tried to get it working with rustls:

use tokio_tungstenite::{Connector, connect_async_tls_with_config};
use rustls::{RootCertStore, ClientConfig};

#[tokio::main]
async fn main() {
    let cert_file = fs::read("certs/localhost.crt.der").unwrap();
    let rust_cert = rustls::Certificate(cert_file);
    let mut root_cert_store = RootCertStore::empty();
    root_cert_store.add(&rust_cert).unwrap();

    let config = ClientConfig::builder()
        .with_safe_defaults()
        .with_root_certificates(root_cert_store)
        .with_no_client_auth();

    let connector = Connector::Rustls(Arc::new(config));

    // Connect to the web socket
    let url = Url::parse("wss://localhost:3000/ws").unwrap();
    let (ws_stream, _) = connect_async_tls_with_config(
        url, None, Some(connector)
    ).await.unwrap();
}

This gives me the following error:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Io(Custom { kind: InvalidData, error: InvalidCertificateData("invalid peer certificate: UnknownIssuer") })', src/main.rs:76:90
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

As it complains about the certificate data I think it might be an issue with converting the localhost.crt PEM file to the DER format (which is needed to create rustls::Certificate). I used this command for the conversion:

openssl x509 -in localhost.crt -outform der -out localhost.crt.der

Thank you very much for reading through all of this and in advance for your help. I appreciate any help or hint to guide me into the right direction :smiley:

I found a solution myself shortly after posting this but I didn't want to delete this topic in case someone else comes across this problem too.

The solution was to load the root certificate, that was used to sign the localhost.crt certificate, instead of the localhost.crt directly. The only thing that needed to be changed was the line that loads the file:

let cert_file = fs::read("certs/rootCA.crt.der").unwrap();

Of course the root certificate also has to be in DER format.

Just for reference this is how I created the self signed certificate:

# Create unencrypted private key and a CSR (certificate signing request)
openssl req -newkey rsa:2048 -nodes -keyout localhost.key -out localhost.csr

# Create self-signed certificate (`localhost.crt`) with the private key and CSR
openssl x509 -signkey localhost.key -in localhost.csr -req -days 365 -out localhost.crt

# Create a self-signed root CA
openssl req -x509 -sha256 -days 1825 -newkey rsa:2048 -keyout rootCA.key -out rootCA.crt

Create file localhost.ext with the following content:

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost

# Sign the CSR (`localhost.csr`) with the root CA certificate and private key
# => this overwrites `localhost.crt` because it gets signed
openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in localhost.csr -out localhost.crt -days 365 -CAcreateserial -extfile localhost.ext

# Convert `localhost.crt` (PEM) to DER
openssl x509 -in localhost.crt -outform der -out localhost.crt.der

# Convert `rootCA.crt` (PEM) to DER
openssl x509 -in rootCA.crt -outform der -out rootCA.crt.der

I hope this will help someone :slight_smile:

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.