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