src/server/ssh/auth.rs
Ref: Size: 3.3 KiB
use std::path::Path;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct AuthorizedKey {
pub key_type: String,
pub key_data: String,
pub comment: Option<String>,
}
pub fn parse_authorized_keys(content: &str) -> Vec<AuthorizedKey> {
content
.lines()
.filter_map(|line| {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
return None;
}
let mut parts = line.splitn(3, ' ');
let key_type = parts.next()?.to_string();
let key_data = parts.next()?.to_string();
let comment = parts.next().map(|s| s.to_string());
Some(AuthorizedKey { key_type, key_data, comment })
})
.collect()
}
pub fn load_authorized_keys(path: &Path) -> Result<Vec<AuthorizedKey>, std::io::Error> {
let content = std::fs::read_to_string(path)?;
Ok(parse_authorized_keys(&content))
}
fn is_rsa_key_type(key_type: &str) -> bool {
matches!(key_type, "ssh-rsa" | "rsa-sha2-256" | "rsa-sha2-512")
}
pub fn is_authorized(keys: &[AuthorizedKey], key_type: &str, key_data: &str) -> bool {
keys.iter().any(|k| {
let type_matches = k.key_type == key_type
|| (is_rsa_key_type(&k.key_type) && is_rsa_key_type(key_type));
type_matches && k.key_data == key_data
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_single_key() {
let content = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITest user@host\n";
let keys = parse_authorized_keys(content);
assert_eq!(keys.len(), 1);
assert_eq!(keys[0].key_type, "ssh-ed25519");
assert_eq!(keys[0].key_data, "AAAAC3NzaC1lZDI1NTE5AAAAITest");
assert_eq!(keys[0].comment.as_deref(), Some("user@host"));
}
#[test]
fn parse_multiple_keys() {
let content = "# alice\nssh-ed25519 AAAA1111 alice@example.com\n\n# bob\nssh-ed25519 AAAA2222 bob@work\n";
let keys = parse_authorized_keys(content);
assert_eq!(keys.len(), 2);
assert_eq!(keys[0].key_data, "AAAA1111");
assert_eq!(keys[1].key_data, "AAAA2222");
}
#[test]
fn skip_comments_and_blanks() {
let content = "# this is a comment\n\n # indented comment\n\nssh-ed25519 AAAAkey user\n";
let keys = parse_authorized_keys(content);
assert_eq!(keys.len(), 1);
}
#[test]
fn key_without_comment() {
let content = "ssh-ed25519 AAAAnocomment\n";
let keys = parse_authorized_keys(content);
assert_eq!(keys.len(), 1);
assert!(keys[0].comment.is_none());
}
#[test]
fn is_authorized_matches() {
let keys = parse_authorized_keys("ssh-ed25519 AAAA1111 alice\nssh-ed25519 AAAA2222 bob\n");
assert!(is_authorized(&keys, "ssh-ed25519", "AAAA1111"));
assert!(is_authorized(&keys, "ssh-ed25519", "AAAA2222"));
assert!(!is_authorized(&keys, "ssh-ed25519", "AAAA9999"));
assert!(!is_authorized(&keys, "ssh-rsa", "AAAA1111"));
}
#[test]
fn rsa_key_type_variants_match() {
let keys = parse_authorized_keys("ssh-rsa AAAARSA user\n");
assert!(is_authorized(&keys, "ssh-rsa", "AAAARSA"));
assert!(is_authorized(&keys, "rsa-sha2-256", "AAAARSA"));
assert!(is_authorized(&keys, "rsa-sha2-512", "AAAARSA"));
assert!(!is_authorized(&keys, "rsa-sha2-256", "WRONG"));
}
}