signstar_request_signature/ssh/
known_hosts.rs1use log::{info, warn};
4use russh::keys::{
5 PublicKey,
6 ssh_key::known_hosts::{Entry, HostPatterns},
7};
8
9pub(crate) fn is_server_known<'a>(
16 entries: impl Iterator<Item = &'a Entry>,
17 host: &str,
18 port: u16,
19 key: &PublicKey,
20) -> bool {
21 for entry in entries {
22 if match entry.host_patterns() {
23 HostPatterns::Patterns(items) => items
24 .iter()
25 .any(|item| item == host || item == &format!("[{host}]:{port}")),
26 HostPatterns::HashedName { salt, hash } => {
27 use hmac::Mac;
28 if let Ok(mut mac) = hmac::Hmac::<sha1::Sha1>::new_from_slice(salt) {
29 mac.update(host.as_bytes());
30 mac.finalize().into_bytes()[..] == hash[..]
31 } else {
32 warn!(
33 "the salt {salt:?} was not of correct size so the entry for host {host} does not match"
34 );
35 false
36 }
37 }
38 } && entry.public_key() == key
39 {
40 return if let Some(marker) = entry.marker() {
41 info!("Found marker {marker} for host {host} but it is not supported.");
42 false
43 } else {
44 true
45 };
46 }
47 }
48 false
49}
50
51#[cfg(test)]
52mod tests {
53 use testresult::TestResult;
54
55 use super::*;
56
57 #[test]
58 fn test_single_entry() -> TestResult {
59 let entry: Entry = "gitlab.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K7GdpNtltOK6vy".parse()?;
60
61 assert!(
62 is_server_known(
63 [entry].iter(),
64 "gitlab.archlinux.org",
65 22,
66 &"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K7GdpNtltOK6vy"
67 .parse()?
68 ),
69 "server should be known since there's one matching entry"
70 );
71
72 Ok(())
73 }
74
75 #[test]
76 fn test_single_entry_with_port() -> TestResult {
77 let entry: Entry = "[gitlab.archlinux.org]:22 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K7GdpNtltOK6vy".parse()?;
78
79 assert!(
80 is_server_known(
81 [entry].iter(),
82 "gitlab.archlinux.org",
83 22,
84 &"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K7GdpNtltOK6vy"
85 .parse()?
86 ),
87 "server should be known since there's one matching entry"
88 );
89
90 Ok(())
91 }
92
93 #[test]
94 fn test_single_revoked_entry() -> TestResult {
95 let entry: Entry = "@revoked gitlab.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K7GdpNtltOK6vy".parse()?;
96
97 assert!(
98 !is_server_known(
99 [entry].iter(),
100 "gitlab.archlinux.org",
101 22,
102 &"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K7GdpNtltOK6vy"
103 .parse()?
104 ),
105 "server should not be known since there's one matching entry but it is revoked"
106 );
107
108 Ok(())
109 }
110
111 #[test]
112 fn test_single_cert_authority_entry() -> TestResult {
113 let entry: Entry = "@cert-authority gitlab.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K7GdpNtltOK6vy".parse()?;
114
115 assert!(
116 !is_server_known(
117 [entry].iter(),
118 "gitlab.archlinux.org",
119 22,
120 &"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K7GdpNtltOK6vy"
121 .parse()?
122 ),
123 "server should not be known since certification authorities are not supported"
124 );
125
126 Ok(())
127 }
128
129 #[test]
130 fn test_not_matching_entry() -> TestResult {
131 let entry: Entry = "gitlab.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K89dpNtltOK6vy".parse()?;
132
133 assert!(
134 !is_server_known(
135 [entry].iter(),
136 "gitlab.archlinux.org",
137 22,
138 &"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K7GdpNtltOK6vy"
139 .parse()?
140 ),
141 "server should not be known since there are no matching entries"
142 );
143
144 Ok(())
145 }
146
147 #[test]
148 fn test_not_matching_port_entry() -> TestResult {
149 let entry: Entry = "[gitlab.archlinux.org]:23 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K89dpNtltOK6vy".parse()?;
150
151 assert!(
152 !is_server_known(
153 [entry].iter(),
154 "gitlab.archlinux.org",
155 22,
156 &"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjT2SuA0k/xc5Cbyp+eBY5uN3bRL2K7GdpNtltOK6vy"
157 .parse()?
158 ),
159 "server should not be known since there are no matching entries"
160 );
161
162 Ok(())
163 }
164
165 #[test]
166 fn test_hashed_entry() -> TestResult {
167 let entry: Entry = "|1|b8LfkX9Y09oxr9MMnQyfC9CtciI=|MnTpZgaon9ON5+hrylyRlq/li3Q= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl".parse()?;
169 assert!(
170 is_server_known(
171 [entry].iter(),
172 "github.com",
173 22,
174 &"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"
175 .parse()?
176 ),
177 "server should be known since there's one matching entry"
178 );
179
180 Ok(())
181 }
182}