Skip to main content

pray_core/
ssh_identity.rs

1use crate::PrayResult;
2
3const USER_FINGERPRINT_ENV_VARS: &[&str] = &[
4    "PRAY_SSH_USER_FINGERPRINT",
5    "SSH_USER_FINGERPRINT",
6    "PRAY_SSH_PUBLISHER",
7];
8
9pub fn normalize_identity(value: &str) -> String {
10    value.trim().to_ascii_uppercase()
11}
12
13pub fn active_ssh_user_fingerprint() -> Option<String> {
14    for name in USER_FINGERPRINT_ENV_VARS {
15        if let Ok(value) = std::env::var(name) {
16            let normalized = normalize_identity(&value);
17            if !normalized.is_empty() {
18                return Some(normalized);
19            }
20        }
21    }
22    None
23}
24
25pub fn signing_identity(label: &str, fingerprint: Option<&str>) -> String {
26    if let Some(fingerprint) = fingerprint {
27        let normalized = normalize_identity(fingerprint);
28        if looks_like_ssh_fingerprint(&normalized) {
29            return normalized;
30        }
31    }
32    label.trim().to_string()
33}
34
35pub fn package_signing_identity(
36    signer: Option<&str>,
37    signer_fingerprint: Option<&str>,
38) -> Option<String> {
39    if let Some(fingerprint) = signer_fingerprint {
40        let normalized = normalize_identity(fingerprint);
41        if looks_like_ssh_fingerprint(&normalized) {
42            return Some(normalized);
43        }
44    }
45    signer
46        .map(str::trim)
47        .filter(|value| !value.is_empty())
48        .map(str::to_string)
49}
50
51pub fn looks_like_ssh_fingerprint(value: &str) -> bool {
52    let upper = normalize_identity(value);
53    upper.starts_with("SHA256:") || upper.starts_with("SHA512:") || upper.starts_with("MD5:")
54}
55
56pub fn fingerprint_matches_allowed(identity: &str, allowed: &[String]) -> bool {
57    if allowed.is_empty() {
58        return false;
59    }
60    let normalized = normalize_identity(identity);
61    allowed
62        .iter()
63        .any(|entry| normalize_identity(entry) == normalized)
64}
65
66pub fn read_env_identity(name: &str) -> Option<String> {
67    std::env::var(name)
68        .ok()
69        .map(|value| normalize_identity(&value))
70        .filter(|value| !value.is_empty())
71}
72
73pub fn ssh_public_key_fingerprint(public_key: &str) -> PrayResult<String> {
74    crate::auth::ssh_public_key_fingerprint_text(public_key)
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn signing_identity_prefers_fingerprint() {
83        assert_eq!(
84            signing_identity("alice@example.com", Some("sha256:abc")),
85            "SHA256:ABC"
86        );
87        assert_eq!(
88            signing_identity("alice@example.com", None),
89            "alice@example.com"
90        );
91    }
92
93    #[test]
94    fn package_signing_identity_prefers_fingerprint_field() {
95        assert_eq!(
96            package_signing_identity(Some("alice@example.com"), Some("sha256:deadbeef"),),
97            Some("SHA256:DEADBEEF".to_string())
98        );
99    }
100
101    #[test]
102    fn package_signing_identity_ignores_non_fingerprint_field() {
103        assert_eq!(
104            package_signing_identity(Some("alice@example.com"), Some("alice@example.com"),),
105            Some("alice@example.com".to_string())
106        );
107    }
108}