Reqwest client rustls-tls cannot establish https connection

I try to connect to a corporate server(api) with reqwest. I have a working implementation in javascript, and I want to try now in rust. But I'm not sure if I do the certificate stuff right. The server supplies 3 .pem files:

client.pem
client_key.pem
root.pem

When I understand the reqwest docs right, than client.pem and client_key.pem are used in an "identity" and the root.pem (ca) cert can be added with
add_root_certificate() method.

I played around with the options and headers a lot, but it does not seem to work. To be honest I don't know how to trace/debug this error. The error message does not help at all ("computer says noooo...")

Is there a way to inspect the request more detailed? I think it would help me to compare it with request sent by the nodejs script (see below). Because this works fine.

my rust code to create a reqwest client/request looks like this:

//use reqwest::blocking::ClientBuilder;
use reqwest::{Certificate, Identity};
use std::fs;
use std::io::Read;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://host123.mycompany.com:1234/endpoint";

    // create a certificate identity
    let mut buf_id = Vec::new();
    fs::File::open("C:\\Users\\myuser\\myhome\\certs\\client_key.pem")?.read_to_end(&mut buf_id)?;
    fs::File::open("C:\\Users\\myuser\\myhome\\certs\\client.pem")?.read_to_end(&mut buf_id)?;
    let id = Identity::from_pem(&buf_id)?;

    // load root (ca) cert
    let mut buf_root = Vec::new();
    fs::File::open("C:\\Users\\myuser\\myhome\\certs\\root.pem")?.read_to_end(&mut buf_root)?;
    let cert = Certificate::from_pem(&buf_root)?;

    let client = reqwest::blocking::Client::builder()
        .use_rustls_tls()
        .identity(id)
        .add_root_certificate(cert)
        .build()?;

    let response = client
        .get(url)
        .header("CONTENT_TYPE", "application/json")
        .header("USER_AGENT", "Windows")
        .header("CONNECTION", "keep_alive")
        .header("X-Foo-Xrfkey", "abcdefghijklmnop")
        .header("X-Foo-User", "UserDirectory=Foo; UserId=bar")
        .query(&[("xrfkey", "abcdefghijklmnop")])
        .send()?;

    match response.status() {
        reqwest::StatusCode::OK => {
            println!("connected!");
            let body = response.text()?;
            println!("BODY:{:?}", body);
        }
        _ => {
            eprintln!("Got response status code: {}", response.status());
        }
    }
    Ok(())
}

I get this error:

Error: reqwest::Error { 
    kind: Request, 
    url: Url { 
        scheme: "https", 
        cannot_be_a_base: false, 
        username: "", 
        password: None, 
        host: Some(Domain("host123.mycompany.com")), 
        port: Some(1234), 
        path: "/endpoint", 
        query: Some("xrfkey=abcdefghijklmnop"), 
        fragment: None 
    }, 
    source: hyper::Error(Connect, 
        Custom { 
            kind: Other, 
            error: Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." } 
        }) 
    }

error: process didn't exit successfully: ... (exit code: 1)

I think I can eliminate a lot of error sources (firewall etc), because my nodejs script can connect successfully from the same computer.

An example script is here:

const https = require('https');
const fs = require('fs');

const options = {
  hostname: 'host123.mycompany.com',
  port: 1234,
  path: '/endpoint?xrfkey=abcdefghijklmnop',
  method: 'GET',
  headers: {
    'x-Foo-xrfkey': 'abcdefghijklmnop',
    'X-Foo-User': 'UserDirectory=Foo; UserId=bar',
  },
  key: fs.readFileSync('C:\\Users\\myuser\\myhome\\certs\\client_key.pem'),
  cert: fs.readFileSync('C:\\Users\\myuser\\myhome\\certs\\client.pem'),
  ca: fs.readFileSync('C:\\Users\\myuser\\myhome\\certs\\root.pem'),
};

let responseData = ''; // To store the response data

https.get(options, (res) => {
  console.log(`Got response: ${res.statusCode}`);

  res.on('data', (chunk) => {
    responseData += chunk; // Collect the response data chunks
  });

  res.on('end', () => {
    try {
      const jsonData = JSON.parse(responseData); // Parse the JSON data
      // Iterate through the array of objects
      jsonData.forEach((item, index) => {
        const { id } = item;
        const { name } = item;
        const { publishTime } = item;
        const { fileSize } = item;
        const ownerUserId = item.owner.userId;
        const streamName = item.stream ? item.stream.name : ownerUserId;
        console.log(`Entry ${index + 1}:`);
        console.log(`ID: ${id}`);
        console.log(`Name: ${name}`);
        console.log(`Publish Time: ${publishTime}`);
        console.log(`Stream Name: ${streamName}`);
        console.log(`File Size: ${parseFloat(fileSize / (1024 ** 2)).toFixed(2)} MB`);
        console.log(`Owner UserId: ${ownerUserId}`);
        console.log('\n');
      });
    } catch (error) {
      console.error('Error parsing JSON:', error.message);
    }
  });
}).on('error', (e) => {
  console.error(`Got error: ${e.message}`);
});

I was not able to solve this directly, but I managed to work around.

Instead of using rustls-tls, I use now native-tls with PKCS12. I saw the server is able to create pfx files too and gave it a shot.

let mut buf = Vec::new();
    fs::File::open("C:\\Users\\myuser\\myhome\\certs\\client.pfx")?.read_to_end(&mut buf)?;
    let id = Identity::from_pkcs12_der(&buf, "password")?;

This works just fine and I can connect.

The PKCS8 in native-tls is also not working with my pem files.
I tried it like this

let cert = fs::read("C:\\Users\\myuser\\myhome\\certs\\client.pem")?;
let key = fs::read("C:\\Users\\myuser\\myhome\\certs\\client_key.pem")?;
let id = Identity::from_pkcs8_pem(&cert, &key)?;

but it gives me

Error: reqwest::Error { 
     kind: Builder, 
     source: Custom { 
        kind: InvalidInput, 
        error: "not a PKCS#8 key" 
     } 
}

If someone has an idea how to solve it, I will give it a try, but for now I stop here and go with the pfx file.

Edit:
I tried the same program (on linux) with another server instance (fresh certs etc) and it seems to work now. I will give it a shot on the windows machine again next week.

Cargo.toml:

[dependencies]
reqwest = { version = "0.11", features = ["json","blocking","rustls-tls"] }
tokio = { version = "1", features = ["full"] }
serde_json = "1.0.105"
1 Like

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.