How can I call a web API using a pfx certificate containing a key and certificate in rust?

71 Views Asked by At

I am attempting to call a web API that requires a certificate and key to access it.

This call works perfectly in Postman where I have a .pfx file installed as a certificate.

The code for calling the API itself seems to work as expected and returns a 401 error.

The problem I am encountering is to do with adding a valid certificate to the client that calls the API.

The decrypted key has a format like this:

Bag Attributes
    localKeyID: 01 00 00 00 
    friendlyName: te-XXXXXXX-3fab-4XXX3-8505-1c7XXXXXXXXXXbb
    Microsoft CSP Name: Microsoft Software Key Storage Provider
Key Attributes
    X509v3 Key Usage: 90 
-----BEGIN PRIVATE KEY-----
// key withheld
-----END PRIVATE KEY-----
Bag Attributes
    localKeyID: 01 00 00 00 
    1.3.6.1.4.1.311.17.3.71: 45 00 43 00 //etc 
subject=CN = companyCert

issuer=CN = companyCert

-----BEGIN CERTIFICATE-----
// cert withheld
-----END CERTIFICATE-----

I have tried reading the PFX file itself and converting it to PEM files as well, neither of which I can get working.

I have tried with both rustls and native_tls with no success.

The closest code I got is: (at least as far as I know)

use native_tls::TlsConnector;
use openssl::pkcs12::Pkcs12;
use openssl::ssl::{SslConnector, SslMethod};
use reqwest::Client;
use std::fs::File;
use std::io::Read;
use tokio;

async fn call_api() -> Result<(), Box<dyn std::error::Error>> {
    let mut buf = vec![];
    File::open("cer.pfx")?.read_to_end(&mut buf)?;

    let pkcs12 = Pkcs12::from_der(&buf)?.parse("password")?;

    let mut ssl_builder = SslConnector::builder(SslMethod::tls())?;
    ssl_builder.set_certificate(&pkcs12.cert)?;
    ssl_builder.set_private_key(&pkcs12.pkey)?;

    let tls_connector = TlsConnector::builder()
        .danger_accept_invalid_certs(true)
        .danger_accept_invalid_hostnames(true)
        .build()?;

    let client = reqwest::Client::builder()
        .use_preconfigured_tls(tls_connector)
        .build()?;

    let mut headers = reqwest::header::HeaderMap::new();
    headers.insert("Content-Type", "application/json".parse()?);
    headers.insert(
        "Authorization",
        "GGL-API-KEY XXXX-XXXX-XXXX-XXXX-XXXX".parse()?,
    );

    let request = client.request(reqwest::Method::GET, "https://example.api")
        .headers(headers);
    println!("request: {:?}", request);

    let response = request.send().await?;
    println!("response: {:?}", response);
    let body = response.text().await?;

    println!("{}", body);

    Ok(())
}

#[tokio::main]
async fn main() {
    match call_api().await {
        Err(e) => eprintln!("Error: {}", e),
        _ => (),
    }
}

This produces:

request: RequestBuilder { method: GET, url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("example.api")), port: None, path: "/api/cardholders", query: Some("fields=example"), fragment: None }, headers: {"content-type": "application/json", "authorization": "GGL-API-KEY XXXX-XXXX-XXXX-XXXX"} }
response: Response { url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("example.api")), port: None, path: "/api/XXXXX", query: Some("fields=example"), fragment: None }, status: 401, headers: {"content-length": "0", "date": "Mon, 19 Feb 2024 04:09:01 GMT"} }

I have this working code in C# that works calling the exact same API with the same Certificate which succeeds without a hitch:

 X509Certificate2 apiCertificate = new X509Certificate2(path, config["CertificatePassword"]);

 handler.ClientCertificateOptions = ClientCertificateOption.Manual;
 handler.ClientCertificates.Add(apiCertificate);

 HttpClient httpClient = new HttpClient(handler);

 httpClient.DefaultRequestHeaders.Add("Authorization", config["ApiKey"]);
 httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
 var myResponse = await httpClient.GetAsync(config["URL"]);
0

There are 0 best solutions below