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"]);