diff options
Diffstat (limited to 'src/ssh-cert-info')
| -rw-r--r-- | src/ssh-cert-info/Cargo.toml | 6 | ||||
| -rw-r--r-- | src/ssh-cert-info/README.org | 1 | ||||
| -rw-r--r-- | src/ssh-cert-info/src/main.rs | 225 |
3 files changed, 0 insertions, 232 deletions
diff --git a/src/ssh-cert-info/Cargo.toml b/src/ssh-cert-info/Cargo.toml deleted file mode 100644 index f00f2c5..0000000 --- a/src/ssh-cert-info/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "ssh-cert-info" -version = "0.1.0" -edition = "2024" - -[dependencies] diff --git a/src/ssh-cert-info/README.org b/src/ssh-cert-info/README.org deleted file mode 100644 index 9d10f5f..0000000 --- a/src/ssh-cert-info/README.org +++ /dev/null @@ -1 +0,0 @@ -A quick way to check information about various SSH certificates on my machine. diff --git a/src/ssh-cert-info/src/main.rs b/src/ssh-cert-info/src/main.rs deleted file mode 100644 index 4c0c1ff..0000000 --- a/src/ssh-cert-info/src/main.rs +++ /dev/null @@ -1,225 +0,0 @@ -use std::env; -use std::fs; -use std::path::PathBuf; -use std::process::Command; - -fn main() { - let ssh_dir = get_ssh_directory(); - - match fs::read_dir(&ssh_dir) { - Ok(entries) => { - let mut cert_files = Vec::new(); - - for entry in entries { - if let Ok(entry) = entry { - let path = entry.path(); - if let Some(extension) = path.extension() { - if extension == "pub" { - // I use the following convention for my certificates: <identificator>-cert.pub - if let Some(filename) = path.file_name() { - if filename.to_string_lossy().contains("-cert.pub") { - cert_files.push(path); - } - } - } - } - } - } - - if cert_files.is_empty() { - println!("No SSH certificates found in {}", ssh_dir.display()); - return; - } - - println!("SSH Certificate Report"); - println!("======================"); - println!(); - - for cert_path in cert_files { - check_certificate(&cert_path); - println!(); - } - } - Err(e) => { - eprintln!("Error reading SSH directory {}: {}", ssh_dir.display(), e); - std::process::exit(1); - } - } -} - -fn get_ssh_directory() -> PathBuf { - let home = env::var("HOME").unwrap_or_else(|_| { - eprintln!("HOME environment variable not set"); - std::process::exit(1); - }); - - PathBuf::from(home).join(".ssh") -} - -fn check_certificate(cert_path: &PathBuf) { - println!("Certificate: {}", cert_path.display()); - - // Use ssh-keygen to get certificate information - // TODO: maybe consider https://docs.rs/ssh-key/latest/ssh_key/certificate/struct.Certificate.html ? - let output = Command::new("ssh-keygen") - .arg("-L") - .arg("-f") - .arg(cert_path) - .output(); - - match output { - Ok(output) => { - if output.status.success() { - let cert_info = String::from_utf8_lossy(&output.stdout); - parse_and_display_cert_info(&cert_info); - } else { - let error = String::from_utf8_lossy(&output.stderr); - println!(" Error: Failed to read certificate: {}", error.trim()); - } - } - Err(e) => { - println!(" Error: Failed to execute ssh-keygen: {}", e); - } - } -} - -fn parse_and_display_cert_info(cert_info: &str) { - let mut type_found = false; - let mut public_key = String::new(); - let mut signing_ca = String::new(); - let mut key_id = String::new(); - let mut serial = String::new(); - let mut valid_from = String::new(); - let mut valid_to = String::new(); - let mut cert_type = String::new(); - let mut principals = Vec::new(); - let mut critical_options = Vec::new(); - let mut extensions = Vec::new(); - - for line in cert_info.lines() { - let line = line.trim(); - - if line.starts_with("Type:") { - cert_type = line.replace("Type:", "").trim().to_string(); - type_found = true; - } else if line.starts_with("Public key:") { - public_key = line.replace("Public key:", "").trim().to_string(); - } else if line.starts_with("Signing CA:") { - signing_ca = line.replace("Signing CA:", "").trim().to_string(); - } else if line.starts_with("Key ID:") { - key_id = line.replace("Key ID:", "").trim().to_string(); - } else if line.starts_with("Serial:") { - serial = line.replace("Serial:", "").trim().to_string(); - } else if line.starts_with("Valid: from") { - // Format: "Valid: from 2023-01-01T00:00:00 to 2024-01-01T00:00:00" - let valid_line = line.replace("Valid: from", "").trim().to_string(); - if let Some(to_pos) = valid_line.find(" to ") { - valid_from = valid_line[..to_pos].trim().to_string(); - valid_to = valid_line[to_pos + 4..].trim().to_string(); - } - } else if line.starts_with("Principals:") { - // Principals might be on the same line or following lines - let principal_text = line.replace("Principals:", "").trim().to_string(); - if !principal_text.is_empty() { - principals.push(principal_text); - } - } else if line.starts_with("Critical Options:") { - let options_text = line.replace("Critical Options:", "").trim().to_string(); - if !options_text.is_empty() { - critical_options.push(options_text); - } - } else if line.starts_with("Extensions:") { - let extensions_text = line.replace("Extensions:", "").trim().to_string(); - if !extensions_text.is_empty() { - extensions.push(extensions_text); - } - } else if !type_found { - // Skip lines before we find the certificate type - continue; - } else if line.len() > 0 && (line.starts_with(" ") || line.starts_with("\t\t")) { - // This might be a continuation of principals, critical options, or extensions - let trimmed = line.trim(); - if !trimmed.is_empty() { - if !principals.is_empty() { - principals.push(trimmed.to_string()); - } else if !critical_options.is_empty() { - critical_options.push(trimmed.to_string()); - } else if !extensions.is_empty() { - extensions.push(trimmed.to_string()); - } - } - } - } - - if !cert_type.is_empty() { - println!(" Type: {}", cert_type); - } - - if !public_key.is_empty() { - println!(" Public key: {}", public_key); - } - - if !signing_ca.is_empty() { - println!(" Signing CA: {}", signing_ca); - } - - if !key_id.is_empty() { - println!(" Key ID: {}", key_id); - } - - if !serial.is_empty() { - println!(" Serial: {}", serial); - } - - if !valid_from.is_empty() && !valid_to.is_empty() { - println!(" Valid from: {}", valid_from); - println!(" Valid to: {}", valid_to); - - check_expiration(&valid_to); - } - - if !principals.is_empty() { - println!(" Principals:"); - for principal in &principals { - if !principal.is_empty() { - println!(" {}", principal); - } - } - } - - if !critical_options.is_empty() { - println!(" Critical Options:"); - for option in &critical_options { - if !option.is_empty() { - println!(" {}", option); - } - } - } - - if !extensions.is_empty() { - println!(" Extensions:"); - for extension in &extensions { - if !extension.is_empty() { - println!(" {}", extension); - } - } - } -} - -fn check_expiration(valid_to: &str) { - // TODO: maybe use chrono for more robust date handling ? - let current_date = std::process::Command::new("date") - .arg("+%Y-%m-%dT%H:%M:%S") - .output(); - - if let Ok(output) = current_date { - if let Ok(current) = String::from_utf8(output.stdout) { - let current = current.trim(); - if valid_to < current { - println!(" ⚠️ WARNING: Certificate has EXPIRED!"); - } else { - println!(" ✓ Certificate is currently valid"); - } - } - } -} |
