use ssh_encoding::{CheckedSum, Decode, Encode, Error as EncodingError, Reader, Writer};
use ssh_key::public::KeyData;
use super::KeyConstraintExtension;
const RESERVED_FIELD: &str = "";
#[derive(Debug, Clone, PartialEq)]
pub struct RestrictDestination {
pub constraints: Vec<DestinationConstraint>,
}
impl Decode for RestrictDestination {
type Error = crate::proto::error::ProtoError;
fn decode(reader: &mut impl Reader) -> Result<Self, Self::Error> {
let mut constraints = Vec::new();
while !reader.is_finished() {
constraints.push(reader.read_prefixed(DestinationConstraint::decode)?);
}
Ok(Self { constraints })
}
}
impl Encode for RestrictDestination {
fn encoded_len(&self) -> ssh_encoding::Result<usize> {
self.constraints.iter().try_fold(0, |acc, e| {
let constraint_len = e.encoded_len_prefixed()?;
usize::checked_add(acc, constraint_len).ok_or(EncodingError::Length)
})
}
fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
for constraint in &self.constraints {
constraint.encode_prefixed(writer)?;
}
Ok(())
}
}
impl KeyConstraintExtension for RestrictDestination {
const NAME: &'static str = "restrict-destination-v00@openssh.com";
}
#[derive(Debug, Clone, PartialEq)]
pub struct HostTuple {
pub username: String,
pub hostname: String,
pub keys: Vec<KeySpec>,
}
impl Decode for HostTuple {
type Error = crate::proto::error::ProtoError;
fn decode(reader: &mut impl Reader) -> Result<Self, Self::Error> {
let username = String::decode(reader)?;
let hostname = String::decode(reader)?;
let _reserved = String::decode(reader)?;
let mut keys = Vec::new();
while !reader.is_finished() {
keys.push(KeySpec::decode(reader)?);
}
Ok(Self {
username,
hostname,
keys,
})
}
}
impl Encode for HostTuple {
fn encoded_len(&self) -> ssh_encoding::Result<usize> {
let prefix = [
self.username.encoded_len()?,
self.hostname.encoded_len()?,
RESERVED_FIELD.encoded_len()?,
]
.checked_sum()?;
self.keys.iter().try_fold(prefix, |acc, e| {
let key_len = e.encoded_len()?;
usize::checked_add(acc, key_len).ok_or(EncodingError::Length)
})
}
fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
self.username.encode(writer)?;
self.hostname.encode(writer)?;
RESERVED_FIELD.encode(writer)?;
for key in &self.keys {
key.encode(writer)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DestinationConstraint {
pub from: HostTuple,
pub to: HostTuple,
}
impl Decode for DestinationConstraint {
type Error = crate::proto::error::ProtoError;
fn decode(reader: &mut impl Reader) -> Result<Self, Self::Error> {
let from = reader.read_prefixed(HostTuple::decode)?;
let to = reader.read_prefixed(HostTuple::decode)?;
let _reserved = String::decode(reader)?;
Ok(Self { from, to })
}
}
impl Encode for DestinationConstraint {
fn encoded_len(&self) -> ssh_encoding::Result<usize> {
[
self.from.encoded_len_prefixed()?,
self.to.encoded_len_prefixed()?,
RESERVED_FIELD.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
self.from.encode_prefixed(writer)?;
self.to.encode_prefixed(writer)?;
RESERVED_FIELD.encode(writer)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct KeySpec {
pub keyblob: KeyData,
pub is_ca: bool,
}
impl Decode for KeySpec {
type Error = crate::proto::error::ProtoError;
fn decode(reader: &mut impl Reader) -> Result<Self, Self::Error> {
let keyblob = reader.read_prefixed(KeyData::decode)?;
Ok(Self {
keyblob,
is_ca: u8::decode(reader)? != 0,
})
}
}
impl Encode for KeySpec {
fn encoded_len(&self) -> ssh_encoding::Result<usize> {
[self.keyblob.encoded_len_prefixed()?, 1u8.encoded_len()?].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
self.keyblob.encode_prefixed(writer)?;
if self.is_ca {
1u8.encode(writer)
} else {
0u8.encode(writer)
}
}
}
#[cfg(test)]
mod tests {
use hex_literal::hex;
use testresult::TestResult;
use super::*;
use crate::proto::ProtoError;
fn round_trip<T>(msg: T) -> TestResult
where
T: Encode + Decode<Error = ProtoError> + std::fmt::Debug + std::cmp::PartialEq,
{
let mut buf: Vec<u8> = vec![];
msg.encode(&mut buf)?;
let mut re_encoded = &buf[..];
let msg2 = T::decode(&mut re_encoded)?;
assert_eq!(msg, msg2);
Ok(())
}
#[test]
fn parse_destination_constraint() -> TestResult {
let mut msg = &hex!(
" 00
0002 6f00 0000 0c00 0000 0000 0000 0000
0000 0000 0002 5700 0000 0000 0000 0a67
6974 6875 622e 636f 6d00 0000 0000 0000
3300 0000 0b73 7368 2d65 6432 3535 3139
0000 0020 e32a aa79 15ce b9b4 49d1 ba50
ea2a 28bb 1a6e 01f9 0bda 245a 2d1d 8769
7d18 a265 0000 0001 9700 0000 0773 7368
2d72 7361 0000 0003 0100 0100 0001 8100
a3ee 774d c50a 3081 c427 8ec8 5c2e ba8f
1228 a986 7b7e 5534 ef0c fea6 1c12 fd8f
568d 5246 3851 ed60 bf09 c62d 594e 8467
98ae 765a 3204 4aeb e3ca 0945 da0d b0bb
aad6 d6f2 0224 84be da18 2b0e aff0 b9e9
224c cbf0 4265 fc5d d675 b300 ec52 0cf8
15b2 67ab 3816 1f36 a96d 57df e158 2a81
cb02 0d21 1fb9 7488 3a25 327b da97 04a4
48dc 6205 e413 6604 1575 7524 79ec 2a06
cb58 d961 49ca 9bd9 49b2 4644 32ca d44b
b4bf b7f1 31b1 9310 9f96 63be e59f 0249
2358 ec68 9d8c c219 ed0e 3332 3036 9f59
c6ae 54c3 933c 030a cc3e c2a1 4f19 0035
efd7 277c 658e 5915 6bba 3d7a cfa5 f2bf
1be3 2706 f3d3 0419 ef95 cae6 d292 6fb1
4dc9 e204 b384 d3e2 393e 4b87 613d e014
0b9c be6c 3622 ad88 0ce0 60bb b849 f3b6
7672 6955 90ec 1dfc d402 b841 daf0 b79d
59a8 4c4a 6d0a 5350 d9fe 123a a84f 0bea
363e 24ab 1e50 5022 344e 14bf 6243 b124
25e6 3d45 996e 18e9 0a0e 7a8b ed9a 07a0
a62b 6246 867e 7b2b 99a3 d0c3 5d05 7038
fd69 f01f a5e8 3d62 732b 9372 bb6c c1de
7019 a7e4 b986 942c fa9d 6f37 5ff0 b239
0000 0000 6800 0000 1365 6364 7361 2d73
6861 322d 6e69 7374 7032 3536 0000 0008
6e69 7374 7032 3536 0000 0041 0449 8a48
4363 4047 b33a 6c64 64cc bba2 92a0 c050
7d9e 4b79 611a d832 336e 1b93 7cee e460
83a0 8bad ba39 c007 53ff 2eaf d262 95d1
4db0 d166 7660 1ffe f93a 6872 4800 0000
0000"
)[..];
let destination_constraint = RestrictDestination::decode(&mut msg)?;
eprintln!("Destination constraint: {destination_constraint:?}");
round_trip(destination_constraint)?;
#[rustfmt::skip]
let mut buffer: &[u8] = const_str::concat_bytes!(
[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",
[0, 0, 0, 12], b"metacode.biz",
[0, 0, 0, 0], [0, 0, 0, 51], [0, 0, 0, 11], b"ssh-ed25519",
[0, 0, 0, 32], [177, 185, 198, 92, 165, 45, 127, 95, 202, 195, 226, 63, 6, 115, 10, 104, 18, 137, 172,
240, 153, 154, 174, 74, 83, 7, 1, 204, 14, 177, 153, 40], [0], [0, 0, 0, 0], );
let destination_constraint = RestrictDestination::decode(&mut buffer)?;
eprintln!("Destination constraint: {destination_constraint:?}");
round_trip(destination_constraint)?;
let mut buffer: &[u8] = &[
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,
0, 0, 0, 10, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 0, 0, 0, 0, 0, 0, 0, 51, 0,
0, 0, 11, 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 227, 42, 170,
121, 21, 206, 185, 180, 73, 209, 186, 80, 234, 42, 40, 187, 26, 110, 1, 249, 11, 218,
36, 90, 45, 29, 135, 105, 125, 24, 162, 101, 0, 0, 0, 0, 0,
];
let destination_constraint = RestrictDestination::decode(&mut buffer)?;
eprintln!("Destination constraint: {destination_constraint:?}");
round_trip(destination_constraint)?;
Ok(())
}
}