use crate::client::RibbitClient;
use crate::cms_parser::{CertificateDetails, PublicKeyInfo};
use crate::error::{Error, Result};
use crate::types::Endpoint;
use base64::Engine;
use der::Decode;
use tracing::{debug, info, warn};
use x509_cert::certificate::Certificate;
pub async fn fetch_certificate_by_ski(
client: &RibbitClient,
ski: &str,
) -> Result<CertificateDetails> {
info!("Fetching certificate for SKI: {}", ski);
let endpoint = Endpoint::Cert(ski.to_string());
let raw_response = client.request_raw(&endpoint).await?;
let response_str = String::from_utf8_lossy(&raw_response);
if !response_str.contains("-----BEGIN CERTIFICATE-----") {
return Err(Error::Asn1Error(
"Response does not contain a PEM certificate".to_string(),
));
}
let cert_start = response_str
.find("-----BEGIN CERTIFICATE-----")
.ok_or_else(|| Error::Asn1Error("Certificate start marker not found".to_string()))?;
let cert_end = response_str
.find("-----END CERTIFICATE-----")
.ok_or_else(|| Error::Asn1Error("Certificate end marker not found".to_string()))?;
let cert_pem = &response_str[cert_start..cert_end + 25];
let lines: Vec<&str> = cert_pem
.lines()
.filter(|line| !line.contains("-----"))
.collect();
let base64_content = lines.join("");
let cert_der = base64::engine::general_purpose::STANDARD
.decode(&base64_content)
.map_err(|e| Error::Asn1Error(format!("Base64 decode error: {e}")))?;
let cert = Certificate::from_der(&cert_der)
.map_err(|e| Error::Asn1Error(format!("Certificate parse error: {e}")))?;
let cert_ski = extract_ski_from_certificate(&cert)?;
if cert_ski != ski {
warn!(
"Certificate SKI mismatch: expected {}, got {}",
ski, cert_ski
);
}
let tbs = &cert.tbs_certificate;
let public_key = PublicKeyInfo {
algorithm: oid_to_algorithm_name(&tbs.subject_public_key_info.algorithm.oid),
key_size: tbs
.subject_public_key_info
.subject_public_key
.raw_bytes()
.len()
* 8,
key_bytes: tbs
.subject_public_key_info
.subject_public_key
.raw_bytes()
.to_vec(),
};
debug!(
"Fetched certificate for {}: {} key, {} bits",
tbs.subject, public_key.algorithm, public_key.key_size
);
Ok(CertificateDetails {
subject: tbs.subject.to_string(),
issuer: tbs.issuer.to_string(),
serial_number: hex::encode(tbs.serial_number.as_bytes()),
public_key: Some(public_key),
})
}
fn extract_ski_from_certificate(cert: &Certificate) -> Result<String> {
if let Some(extensions) = &cert.tbs_certificate.extensions {
for ext in extensions {
if ext.extn_id.to_string() == "2.5.29.14" {
let ski_bytes = ext.extn_value.as_bytes();
if ski_bytes.len() > 2 && ski_bytes[0] == 0x04 {
return Ok(hex::encode(&ski_bytes[2..]));
}
}
}
}
Err(Error::Asn1Error(
"No Subject Key Identifier found in certificate".to_string(),
))
}
fn oid_to_algorithm_name(oid: &der::asn1::ObjectIdentifier) -> String {
match oid.to_string().as_str() {
"1.2.840.113549.1.1.1" => "RSA".to_string(),
"1.2.840.10045.2.1" => "ECDSA".to_string(),
_ => format!("OID: {oid}"),
}
}
pub async fn fetch_signer_certificate(
client: &RibbitClient,
signer_ski: &str,
) -> Result<(CertificateDetails, PublicKeyInfo)> {
let cert_details = fetch_certificate_by_ski(client, signer_ski).await?;
let public_key = cert_details
.public_key
.clone()
.ok_or_else(|| Error::Asn1Error("No public key in certificate".to_string()))?;
Ok((cert_details, public_key))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_oid_to_algorithm_name() {
use der::asn1::ObjectIdentifier;
let rsa_oid = ObjectIdentifier::new("1.2.840.113549.1.1.1").unwrap();
assert_eq!(oid_to_algorithm_name(&rsa_oid), "RSA");
let ecdsa_oid = ObjectIdentifier::new("1.2.840.10045.2.1").unwrap();
assert_eq!(oid_to_algorithm_name(&ecdsa_oid), "ECDSA");
}
}