Rustls: Connecting without certificate in local network

Hi,
I new to rust and I would like to rewrite my program in rust. But one important requirement is to connect to an actor in my local home WIFI. The actor uses for communication a TLS connection.
I have no certificate or servername for the actor, I can reach it with it's IP: 192.168.0.3:52100.
In C, with openssl, I've done it in this way:

const char *server = "192.168.0.3:51200";

// Login mit PW -Request
#define REQLEN 39
const uint8_t req[REQLEN] = {192, 0, 35, 48, 0, 88, 50, 90, 78, 66, 75, 112, 100, 72, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 192};

#define RESLEN 8
const uint8_t res[RESLEN] = {0xc0, 0x00, 0x04, 0x30, 0x01, 0x00, 0x35, 0xc0};

static void logIntoklf()
{
    printf("openssl init done.\n");
    ctx = SSL_CTX_new(SSLv23_client_method());
    SSL *ssl = NULL;
    bio = BIO_new_ssl_connect(ctx);
    check("BIO_new_ssl_connect", bio != NULL);
    BIO_get_ssl(bio, &ssl);
    check("BIO_get_ssl", ssl != NULL);
    if (server_verify)
        SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL);
    else
        SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);

    SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

    BIO_set_conn_hostname(bio, server);
    out = BIO_new_fp(stdout, BIO_NOCLOSE);
    check("BIO_do_connect", BIO_do_connect(bio) > 0);
    check("BIO_write", BIO_write(bio, req, REQLEN) == REQLEN);
    uint8_t buf[100];
    int len = BIO_read(bio, buf, sizeof(buf));
    check("BIO_read", len == RESLEN);
    check("response data", memcmp(res, buf, RESLEN) == 0);

    // KLF200 hängt beim BIO_ssl_shutdown(bio); => vermutlich ein KLF Problem

    freeall();
    printf("Ende\n");
}

Now, I tried it with rustls like this (it's the client example with minimal changes):

fn connect_velux() {
    let mut root_store = RootCertStore::empty();
    root_store.add_server_trust_anchors(
        webpki_roots::TLS_SERVER_ROOTS
            .0
            .iter()
            .map(|ta| {
                OwnedTrustAnchor::from_subject_spki_name_constraints(
                    ta.subject,
                    ta.spki,
                    ta.name_constraints,
                )
            }),
    );
    let config = rustls::ClientConfig::builder()
        .with_safe_defaults()
        .with_root_certificates(root_store)
        .with_no_client_auth();

    let server_name = "VELUX_KLF_3139".try_into().unwrap();
    let mut conn = rustls::ClientConnection::new(std::sync::Arc::new(config), server_name).unwrap();
    let mut sock = std::net::TcpStream::connect("192.168.0.3:51200").unwrap();
    println!("TCP established");
    let mut tls = rustls::Stream::new(&mut conn, &mut sock);
    let txbytes : Vec<u8> = vec![192, 0, 35, 48, 0, 88, 50, 90, 78, 66, 75, 112, 100, 72, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 192];
    println!("{:?}", txbytes.as_slice());
    tls.write_all(txbytes.as_slice()).unwrap();
    println!("not reached.");
    let ciphersuite = tls
        .conn
        .negotiated_cipher_suite()
        .unwrap();
    writeln!(
        &mut std::io::stderr(),
        "Current ciphersuite: {:?}",
        ciphersuite.suite()
    )
    .unwrap();
    let mut plaintext = Vec::new();
    tls.read_to_end(&mut plaintext).unwrap();
    println!("{:?}", plaintext);
//    std::io::stdout().write_all(&plaintext).unwrap();
}

It doesn't work, I get Err value: Custom { kind: InvalidData, error: InvalidCertificateData("invalid peer certificate: CaUsedAsEndEntity")

If I change the server_name:
let server_name = "192.168.0.3".try_into().unwrap();
it gives another errror: Custom { kind: InvalidData, error: UnsupportedNameType }

Would great if somebody here has a hint how to communicate only with IP over TLS in Rust. I would prefer to stick to rustls because I want to crosscompile the project for my raspberry pi.

In your C code, you turn off TLS certificate verification. This means that you lose most of the benefits of TLS; you have no way to verify that the server you're talking to is the one you intend to talk to, and is usually a security bug as a result.

The recommended way to deal with this is to run a private CA (lots of options for this - from the openssl ca command and some careful scripting through something like Dogtag PKI, all the way to running Microsoft Active DIrectory, FreeIPA or similar systsems), and add the CA's certificate to your list of server trust anchors.

If you really understand the implications of doing so for security (noting that if you do this badly, you might as well not bother with TLS, since a bad setup results in an attacker being able to MITM the connection), you can implement a custom server cert verifier that correctly verifies your certificate with custom rules.

Thanks for your security hints. But...As mentioned, the SSL connection is inside my private WIFI network. That's secure enough. I'm not a security expert but anyway I'm sure that I don't need a secure connection. I'm just forced to use TLS because my actor has only a TLS interface.

Meanwhile I got this rust code to work (uses openssl crate):

fn connect_velux2() {
    let mut connector_builder = SslConnector::builder(SslMethod::tls()).unwrap();
    connector_builder.set_verify(SslVerifyMode::NONE);
    let connector = connector_builder.build(); //.set_verify(SslVerifyMode::NONE);

    let tcp_stream = TcpStream::connect("192.168.0.3:51200").unwrap();
    let mut stream = connector.connect("192.168.0.3", tcp_stream).unwrap();
    
    let txbytes:[u8;39] = [192, 0, 35, 48, 0, 88, 50, 90, 78, 66, 75, 112, 100, 72, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 192];
    stream.write_all(&txbytes).unwrap();
    thread::sleep(std::time::Duration::from_secs(1));
    let mut res = vec![0u8; 200];
    let rxlen = stream.read(&mut res).unwrap();
    res.resize(rxlen, 0);
    println!("{:?}", res); // prints [192, 0, 4, 48, 1, 0, 53, 192]
}

Very cool, but not enough. I want to cross compile for my raspberry pi. It would a lot of easier to crosscompile with rustls. Sooo..if there would be a solution for rustls, thats great.

BTW: I've no certificates for my actor...it's a velux klf200.

This custom server cert verifier could be the solution. Can you provide a line of code how to use it?

It's complex because it's dangerous. You need to define a struct that implements ServerCertVerifier; in that trait implementation, you need to define the function verify_server_cert and have it return Ok(ServerCertVerified::assertion()) if you're happy with the certificate you're passed (this you need to define for your project - you could accept any nonsense, you could validate certificate parsing but not validity, or more complex).

Then, when building your ClientConfig, you use with_custom_certificate_verifier to tell rustls to use your custom verifier with your custom rules instead of the default.

Ok, I got it. Also I had to add the dagerous_configuration in my Cargo.toml:

rustls = { version = "*", features = ["dangerous_configuration"] }

It took some moments to get it right because I didn't find in the docs for ServerCertVerifier that I've to do so. Luckily I found this page: https://quinn-rs.github.io/quinn/quinn/certificate.html

The final result (see below) runs on my amd64 PC and also on my raspberry pi. Great!

I append it to save time for the next rust beginner who want to use unsecure SSL :wink:

use std::io::{Write, Read};

// see https://quinn-rs.github.io/quinn/quinn/certificate.html
struct SkipServerVerification;

impl SkipServerVerification {
    fn new() -> std::sync::Arc<Self> {
        std::sync::Arc::new(Self)
    }
}

impl rustls::client::ServerCertVerifier for SkipServerVerification {
    fn verify_server_cert(
        &self,
        _end_entity: &rustls::Certificate,
        _intermediates: &[rustls::Certificate],
        _server_name: &rustls::ServerName,
        _scts: &mut dyn Iterator<Item = &[u8]>,
        _ocsp_response: &[u8],
        _now: std::time::SystemTime,
    ) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
        Ok(rustls::client::ServerCertVerified::assertion())
    }
}
fn connect_velux() {
    let config = rustls::ClientConfig::builder()
    .with_safe_defaults()
    .with_custom_certificate_verifier(SkipServerVerification::new())
    .with_no_client_auth();


    let server_name = "192.168.0.3".try_into().unwrap();
    let mut conn = rustls::ClientConnection::new(std::sync::Arc::new(config), server_name).unwrap();
    let mut sock = std::net::TcpStream::connect("192.168.0.3:51200").unwrap();
    let mut tls = rustls::Stream::new(&mut conn, &mut sock);
    let txbytes : Vec<u8> = vec![192, 0, 35, 48, 0, 88, 50, 90, 78, 66, 75, 112, 100, 72, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 192];
    println!("tx: {:?}", txbytes.as_slice());
    tls.write_all(txbytes.as_slice()).unwrap();
    let ciphersuite = tls
        .conn
        .negotiated_cipher_suite()
        .unwrap();
    writeln!(
        &mut std::io::stderr(),
        "Current ciphersuite: {:?}",
        ciphersuite.suite()
    )
    .unwrap();
    std::thread::sleep(std::time::Duration::from_millis(3000));
    let mut rxdata = vec![0u8; 200];
    let rxlen = tls.read(&mut rxdata).unwrap();
    rxdata.resize(rxlen, 0);
    println!("rxed: {:?}", rxdata);
}

fn main() {
    connect_velux();
}

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.