1use ssh_encoding::{CheckedSum, Decode, Encode, Error as EncodingError, Reader, Writer};
7use ssh_key::public::KeyData;
8
9use super::KeyConstraintExtension;
10
11const RESERVED_FIELD: &str = "";
13
14#[derive(Debug, Clone, PartialEq)]
24pub struct RestrictDestination {
25 pub constraints: Vec<DestinationConstraint>,
27}
28
29impl Decode for RestrictDestination {
30 type Error = crate::proto::error::ProtoError;
31
32 fn decode(reader: &mut impl Reader) -> Result<Self, Self::Error> {
33 let mut constraints = Vec::new();
34 while !reader.is_finished() {
35 constraints.push(reader.read_prefixed(DestinationConstraint::decode)?);
36 }
37 Ok(Self { constraints })
38 }
39}
40
41impl Encode for RestrictDestination {
42 fn encoded_len(&self) -> ssh_encoding::Result<usize> {
43 self.constraints.iter().try_fold(0, |acc, e| {
44 let constraint_len = e.encoded_len_prefixed()?;
45 usize::checked_add(acc, constraint_len).ok_or(EncodingError::Length)
46 })
47 }
48
49 fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
50 for constraint in &self.constraints {
51 constraint.encode_prefixed(writer)?;
52 }
53 Ok(())
54 }
55}
56
57impl KeyConstraintExtension for RestrictDestination {
58 const NAME: &'static str = "restrict-destination-v00@openssh.com";
59}
60
61#[derive(Debug, Clone, PartialEq)]
67pub struct HostTuple {
68 pub username: String,
70
71 pub hostname: String,
73
74 pub keys: Vec<KeySpec>,
76}
77
78impl Decode for HostTuple {
79 type Error = crate::proto::error::ProtoError;
80
81 fn decode(reader: &mut impl Reader) -> Result<Self, Self::Error> {
82 let username = String::decode(reader)?;
83 let hostname = String::decode(reader)?;
84 let _reserved = String::decode(reader)?;
85
86 let mut keys = Vec::new();
87 while !reader.is_finished() {
88 keys.push(KeySpec::decode(reader)?);
89 }
90
91 Ok(Self {
92 username,
93 hostname,
94 keys,
95 })
96 }
97}
98
99impl Encode for HostTuple {
100 fn encoded_len(&self) -> ssh_encoding::Result<usize> {
101 let prefix = [
102 self.username.encoded_len()?,
103 self.hostname.encoded_len()?,
104 RESERVED_FIELD.encoded_len()?,
105 ]
106 .checked_sum()?;
107 self.keys.iter().try_fold(prefix, |acc, e| {
108 let key_len = e.encoded_len()?;
109 usize::checked_add(acc, key_len).ok_or(EncodingError::Length)
110 })
111 }
112
113 fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
114 self.username.encode(writer)?;
115 self.hostname.encode(writer)?;
116 RESERVED_FIELD.encode(writer)?;
117 for key in &self.keys {
118 key.encode(writer)?;
119 }
120 Ok(())
121 }
122}
123
124#[derive(Debug, Clone, PartialEq)]
133pub struct DestinationConstraint {
134 pub from: HostTuple,
136
137 pub to: HostTuple,
139}
140
141impl Decode for DestinationConstraint {
142 type Error = crate::proto::error::ProtoError;
143
144 fn decode(reader: &mut impl Reader) -> Result<Self, Self::Error> {
145 let from = reader.read_prefixed(HostTuple::decode)?;
146 let to = reader.read_prefixed(HostTuple::decode)?;
147 let _reserved = String::decode(reader)?;
148
149 Ok(Self { from, to })
150 }
151}
152
153impl Encode for DestinationConstraint {
154 fn encoded_len(&self) -> ssh_encoding::Result<usize> {
155 [
156 self.from.encoded_len_prefixed()?,
157 self.to.encoded_len_prefixed()?,
158 RESERVED_FIELD.encoded_len()?,
159 ]
160 .checked_sum()
161 }
162
163 fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
164 self.from.encode_prefixed(writer)?;
165 self.to.encode_prefixed(writer)?;
166 RESERVED_FIELD.encode(writer)?;
167 Ok(())
168 }
169}
170
171#[derive(Debug, Clone, PartialEq)]
181pub struct KeySpec {
182 pub keyblob: KeyData,
184
185 pub is_ca: bool,
187}
188
189impl Decode for KeySpec {
190 type Error = crate::proto::error::ProtoError;
191
192 fn decode(reader: &mut impl Reader) -> Result<Self, Self::Error> {
193 let keyblob = reader.read_prefixed(KeyData::decode)?;
194 Ok(Self {
195 keyblob,
196 is_ca: u8::decode(reader)? != 0,
197 })
198 }
199}
200
201impl Encode for KeySpec {
202 fn encoded_len(&self) -> ssh_encoding::Result<usize> {
203 [self.keyblob.encoded_len_prefixed()?, 1u8.encoded_len()?].checked_sum()
204 }
205
206 fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
207 self.keyblob.encode_prefixed(writer)?;
208 if self.is_ca {
211 1u8.encode(writer)
212 } else {
213 0u8.encode(writer)
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use hex_literal::hex;
221 use testresult::TestResult;
222
223 use super::*;
224 use crate::proto::ProtoError;
225
226 fn round_trip<T>(msg: T) -> TestResult
227 where
228 T: Encode + Decode<Error = ProtoError> + std::fmt::Debug + std::cmp::PartialEq,
229 {
230 let mut buf: Vec<u8> = vec![];
231 msg.encode(&mut buf)?;
232 let mut re_encoded = &buf[..];
233
234 let msg2 = T::decode(&mut re_encoded)?;
235 assert_eq!(msg, msg2);
236
237 Ok(())
238 }
239
240 #[test]
241 fn parse_destination_constraint() -> TestResult {
242 let mut msg = &hex!(
243 " 00
244 0002 6f00 0000 0c00 0000 0000 0000 0000
245 0000 0000 0002 5700 0000 0000 0000 0a67
246 6974 6875 622e 636f 6d00 0000 0000 0000
247 3300 0000 0b73 7368 2d65 6432 3535 3139
248 0000 0020 e32a aa79 15ce b9b4 49d1 ba50
249 ea2a 28bb 1a6e 01f9 0bda 245a 2d1d 8769
250 7d18 a265 0000 0001 9700 0000 0773 7368
251 2d72 7361 0000 0003 0100 0100 0001 8100
252 a3ee 774d c50a 3081 c427 8ec8 5c2e ba8f
253 1228 a986 7b7e 5534 ef0c fea6 1c12 fd8f
254 568d 5246 3851 ed60 bf09 c62d 594e 8467
255 98ae 765a 3204 4aeb e3ca 0945 da0d b0bb
256 aad6 d6f2 0224 84be da18 2b0e aff0 b9e9
257 224c cbf0 4265 fc5d d675 b300 ec52 0cf8
258 15b2 67ab 3816 1f36 a96d 57df e158 2a81
259 cb02 0d21 1fb9 7488 3a25 327b da97 04a4
260 48dc 6205 e413 6604 1575 7524 79ec 2a06
261 cb58 d961 49ca 9bd9 49b2 4644 32ca d44b
262 b4bf b7f1 31b1 9310 9f96 63be e59f 0249
263 2358 ec68 9d8c c219 ed0e 3332 3036 9f59
264 c6ae 54c3 933c 030a cc3e c2a1 4f19 0035
265 efd7 277c 658e 5915 6bba 3d7a cfa5 f2bf
266 1be3 2706 f3d3 0419 ef95 cae6 d292 6fb1
267 4dc9 e204 b384 d3e2 393e 4b87 613d e014
268 0b9c be6c 3622 ad88 0ce0 60bb b849 f3b6
269 7672 6955 90ec 1dfc d402 b841 daf0 b79d
270 59a8 4c4a 6d0a 5350 d9fe 123a a84f 0bea
271 363e 24ab 1e50 5022 344e 14bf 6243 b124
272 25e6 3d45 996e 18e9 0a0e 7a8b ed9a 07a0
273 a62b 6246 867e 7b2b 99a3 d0c3 5d05 7038
274 fd69 f01f a5e8 3d62 732b 9372 bb6c c1de
275 7019 a7e4 b986 942c fa9d 6f37 5ff0 b239
276 0000 0000 6800 0000 1365 6364 7361 2d73
277 6861 322d 6e69 7374 7032 3536 0000 0008
278 6e69 7374 7032 3536 0000 0041 0449 8a48
279 4363 4047 b33a 6c64 64cc bba2 92a0 c050
280 7d9e 4b79 611a d832 336e 1b93 7cee e460
281 83a0 8bad ba39 c007 53ff 2eaf d262 95d1
282 4db0 d166 7660 1ffe f93a 6872 4800 0000
283 0000"
284 )[..];
285
286 let destination_constraint = RestrictDestination::decode(&mut msg)?;
287 eprintln!("Destination constraint: {destination_constraint:?}");
288
289 round_trip(destination_constraint)?;
290
291 #[rustfmt::skip]
292 let mut buffer: &[u8] = const_str::concat_bytes!(
293 [0, 0, 0, 110], [0, 0, 0, 12], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 86], [0, 0, 0, 6], b"wiktor",
301 [0, 0, 0, 12], b"metacode.biz",
302 [0, 0, 0, 0], [0, 0, 0, 51], [0, 0, 0, 11], b"ssh-ed25519",
306 [0, 0, 0, 32], [177, 185, 198, 92, 165, 45, 127, 95, 202, 195, 226, 63, 6, 115, 10, 104, 18, 137, 172,
308 240, 153, 154, 174, 74, 83, 7, 1, 204, 14, 177, 153, 40], [0], [0, 0, 0, 0], );
312
313 let destination_constraint = RestrictDestination::decode(&mut buffer)?;
314 eprintln!("Destination constraint: {destination_constraint:?}");
315
316 round_trip(destination_constraint)?;
317
318 let mut buffer: &[u8] = &[
319 0, 0, 0, 102, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 0, 0, 0, 0,
320 0, 0, 0, 10, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 0, 0, 0, 0, 0, 0, 0, 51, 0,
321 0, 0, 11, 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 227, 42, 170,
322 121, 21, 206, 185, 180, 73, 209, 186, 80, 234, 42, 40, 187, 26, 110, 1, 249, 11, 218,
323 36, 90, 45, 29, 135, 105, 125, 24, 162, 101, 0, 0, 0, 0, 0,
324 ];
325 let destination_constraint = RestrictDestination::decode(&mut buffer)?;
326 eprintln!("Destination constraint: {destination_constraint:?}");
327
328 round_trip(destination_constraint)?;
329
330 Ok(())
331 }
332}