sfackler / rust-native-tls

Apache License 2.0
471 stars 195 forks source link

`add_root_certificate` does not work when `SSL_CERT_FILE` env var is not set #175

Closed davidMcneil closed 4 years ago

davidMcneil commented 4 years ago

add_root_certificate does not seem to add a certificate when SSL_CERT_FILE is not set or points to an invalid path. Consider the example program below using reqwest.

[dependencies]
reqwest = { version = "*", features = ["blocking", "native-tls", "native-tls-vendored"] }
use reqwest::{blocking::ClientBuilder, Certificate};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cert = "/usr/lib/ssl/certs/ca-certificates.crt";
    let cert = std::fs::read(cert)?;
    let cert = Certificate::from_pem(&cert)?;
    let client = ClientBuilder::new().add_root_certificate(cert).build()?;

    println!("SSL_CERT_FILE {:?}", std::env::var("SSL_CERT_FILE"));
    println!("SSL_CERT_DIR {:?}", std::env::var("SSL_CERT_DIR"));

    let url = "https://www.rust-lang.org/";
    let response = client.get(url).send()?;
    println!("Status {}", response.status());
    Ok(())
}

This works fine when running cargo run. However, running env SSL_CERT_FILE=/a/bad/path cargo run produces the error unable to get local issuer certificate. Switching to use rustls instead of the native-tls backend works.

[dependencies]
reqwest = { version = "*", default-features = false, features = ["blocking", "rustls-tls"] }

This is obviously a very contrived example, but this situation does arise when vendoring openssl with the vendored feature and running on a system without openssl installed. In this case, openssl-probe is unable to set the SSL_CERT_FILE env var leading to the same behavior as when it is set to an invalid path.

sfackler commented 4 years ago

That code is only parsing the first certificate out of the ca-certificates.crt file. Is www.rust-lang.org's root the first entry in that file?

davidMcneil commented 4 years ago

Aha! That explains it. Thank you! ... For those interested the working code that uses the pem crate can be found below. Would you be interested in a PR that adds the ability to make a Vec of Certificates from a buffer? I would essentially copy the functionality from rustls here.

Thanks again! I really apreciate your time.

use reqwest::{blocking::ClientBuilder, Certificate};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = ClientBuilder::new();

    let certs = "/usr/lib/ssl/certs/ca-certificates.crt";
    let certs = std::fs::read(certs)?;
    let certs = pem::parse_many(certs);
    for cert in certs.iter() {
        let cert = Certificate::from_der(&cert.contents)?;
        client = client.add_root_certificate(cert);
    }

    let client = client.build()?;

    println!("SSL_CERT_FILE {:?}", std::env::var("SSL_CERT_FILE"));
    println!("SSL_CERT_DIR {:?}", std::env::var("SSL_CERT_DIR"));

    let url = "https://www.rust-lang.org/";
    let response = client.get(url).send()?;
    println!("Status {}", response.status());
    Ok(())
}
davidMcneil commented 4 years ago

Looks like someone beat me to it in #168.