use openssl::x509::X509;
pub fn discover_ocsp_responder_urls(cert: &X509) -> Vec<String> {
let mut urls = Vec::new();
if let Ok(text_bytes) = cert.to_text() {
if let Ok(text_str) = std::str::from_utf8(&text_bytes) {
urls = extract_ocsp_urls_from_text(text_str);
}
} else if let Ok(pem) = cert.to_pem()
&& let Ok(pem_str) = std::str::from_utf8(&pem)
{
urls = extract_ocsp_urls_from_text(pem_str);
}
urls
}
fn extract_ocsp_urls_from_text(text: &str) -> Vec<String> {
let mut urls = Vec::new();
let mut seen = std::collections::HashSet::new();
let lines: Vec<&str> = text.lines().collect();
for (i, line) in lines.iter().enumerate() {
if line.contains("Authority Info Access") {
let search_end = std::cmp::min(i + 20, lines.len());
for (j, next_line) in lines
.iter()
.enumerate()
.skip(i + 1)
.take(search_end - i - 1)
{
if next_line.trim_start().starts_with("URI:")
&& let Some(url) = extract_url_from_line(next_line)
&& seen.insert(url.clone())
{
urls.push(url);
}
if j > i + 1 && next_line.contains("X509v3") {
break;
}
}
}
}
urls
}
fn extract_url_from_line(line: &str) -> Option<String> {
if let Some(uri_pos) = line.find("URI:") {
let after_uri = &line[uri_pos + 4..].trim_start();
let end_pos = after_uri
.find(|c: char| c.is_whitespace() || c == ',' || c == ';')
.unwrap_or(after_uri.len());
let url = &after_uri[..end_pos].trim();
if url.starts_with("http://") || url.starts_with("https://") {
Some(url.to_string())
} else {
None
}
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_url_from_line_valid_http() {
let line = " OCSP - URI:http://ocsp.example.com/";
let url = extract_url_from_line(line);
assert_eq!(url, Some("http://ocsp.example.com/".to_string()));
}
#[test]
fn test_extract_url_from_line_valid_https() {
let line = " OCSP - URI:https://ocsp.example.com/, Other:value";
let url = extract_url_from_line(line);
assert_eq!(url, Some("https://ocsp.example.com/".to_string()));
}
#[test]
fn test_extract_url_from_line_invalid_protocol() {
let line = " OCSP - URI:ftp://ocsp.example.com/";
let url = extract_url_from_line(line);
assert_eq!(url, None);
}
#[test]
fn test_extract_url_from_line_no_uri() {
let line = " Some other line";
let url = extract_url_from_line(line);
assert_eq!(url, None);
}
#[test]
fn test_extract_url_from_line_with_whitespace() {
let line = " URI:http://ocsp.example.com/path ";
let url = extract_url_from_line(line);
assert_eq!(url, Some("http://ocsp.example.com/path".to_string()));
}
#[test]
fn test_extract_ocsp_urls_empty_text() {
let text = "";
let urls = extract_ocsp_urls_from_text(text);
assert!(urls.is_empty());
}
#[test]
fn test_extract_ocsp_urls_no_aia_section() {
let text = r#"
Certificate:
Data:
Subject: CN=example.com
"#;
let urls = extract_ocsp_urls_from_text(text);
assert!(urls.is_empty());
}
}