ssh_agent_lib/proto/message/add_remove/
constrained.rs

1use ssh_encoding::{self, CheckedSum, Decode, Encode, Reader, Writer};
2use ssh_key::Error as KeyError;
3
4use crate::proto::{AddIdentity, Error, Extension, Result, SmartcardKey, Unparsed};
5
6/// A key constraint, used to place limitations on how and where a key can be used.
7///
8/// Key constraints are set along with a key when are added to an agent.
9///
10/// Specifically, they appear in special `SSH_AGENTC_ADD_*` message variants:
11/// - [`Request::AddIdConstrained`](crate::proto::Request::AddIdConstrained)
12/// - [`Request::AddSmartcardKeyConstrained`](crate::proto::Request::AddSmartcardKeyConstrained)
13#[derive(Clone, PartialEq, Debug)]
14pub enum KeyConstraint {
15    /// Limit the key's lifetime by deleting it after the specified duration (in seconds)
16    Lifetime(u32),
17
18    /// Require explicit user confirmation for each private key operation using the key.
19    Confirm,
20
21    /// Experimental or private-use constraints
22    ///
23    /// Contains:
24    /// - An extension name indicating the type of the constraint (as a UTF-8 string).
25    /// - Extension-specific content
26    ///
27    /// Extension names should be suffixed by the implementation domain
28    /// as per [RFC4251 § 4.2](https://www.rfc-editor.org/rfc/rfc4251.html#section-4.2),
29    /// e.g. "foo@example.com"
30    Extension(Extension),
31}
32
33impl Decode for KeyConstraint {
34    type Error = Error;
35
36    fn decode(reader: &mut impl Reader) -> Result<Self> {
37        let constraint_type = u8::decode(reader)?;
38        // see: https://www.ietf.org/archive/id/draft-miller-ssh-agent-12.html#section-5.2
39        Ok(match constraint_type {
40            1 => KeyConstraint::Lifetime(u32::decode(reader)?),
41            2 => KeyConstraint::Confirm,
42            255 => {
43                let name = String::decode(reader)?;
44                let details: Vec<u8> = Vec::decode(reader)?;
45                KeyConstraint::Extension(Extension {
46                    name,
47                    details: Unparsed::from(details),
48                })
49            }
50            _ => return Err(KeyError::AlgorithmUnknown)?, // FIXME: it should be our own type
51        })
52    }
53}
54
55impl Encode for KeyConstraint {
56    fn encoded_len(&self) -> ssh_encoding::Result<usize> {
57        let base = u8::MAX.encoded_len()?;
58
59        match self {
60            Self::Lifetime(lifetime) => base
61                .checked_add(lifetime.encoded_len()?)
62                .ok_or(ssh_encoding::Error::Length),
63            Self::Confirm => Ok(base),
64            Self::Extension(extension) => [
65                base,
66                extension.name.encoded_len()?,
67                extension.details.encoded_len_prefixed()?,
68            ]
69            .checked_sum(),
70        }
71    }
72
73    fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
74        match self {
75            Self::Lifetime(lifetime) => {
76                1u8.encode(writer)?;
77                lifetime.encode(writer)
78            }
79            Self::Confirm => 2u8.encode(writer),
80            Self::Extension(extension) => {
81                255u8.encode(writer)?;
82                extension.name.encode(writer)?;
83                extension.details.encode_prefixed(writer)
84            }
85        }
86    }
87}
88
89/// Add a key to an agent, with constraints on it's use.
90///
91/// This structure is sent in a [`Request::AddIdConstrained`](crate::proto::Request::AddIdConstrained) (`SSH_AGENTC_ADD_ID_CONSTRAINED`) message.
92///
93/// This is a variant of [`Request::AddIdentity`](crate::proto::Request::AddIdentity) with a set of [`KeyConstraint`]s attached.
94///
95/// Described in [draft-miller-ssh-agent-14 § 3.2](https://www.ietf.org/archive/id/draft-miller-ssh-agent-14.html#section-3.2)
96#[derive(Clone, PartialEq, Debug)]
97pub struct AddIdentityConstrained {
98    /// The credential to be added to the agent.
99    pub identity: AddIdentity,
100
101    /// Constraints to be placed on the `identity`.
102    pub constraints: Vec<KeyConstraint>,
103}
104
105impl Decode for AddIdentityConstrained {
106    type Error = Error;
107
108    fn decode(reader: &mut impl Reader) -> Result<Self> {
109        let identity = AddIdentity::decode(reader)?;
110        let mut constraints = vec![];
111
112        while !reader.is_finished() {
113            constraints.push(KeyConstraint::decode(reader)?);
114        }
115
116        Ok(Self {
117            identity,
118            constraints,
119        })
120    }
121}
122
123impl Encode for AddIdentityConstrained {
124    fn encoded_len(&self) -> ssh_encoding::Result<usize> {
125        self.constraints
126            .iter()
127            .try_fold(self.identity.encoded_len()?, |acc, e| {
128                let constraint_len = e.encoded_len()?;
129                usize::checked_add(acc, constraint_len).ok_or(ssh_encoding::Error::Length)
130            })
131    }
132
133    fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
134        self.identity.encode(writer)?;
135        for constraint in &self.constraints {
136            constraint.encode(writer)?;
137        }
138        Ok(())
139    }
140}
141
142/// Add a key in a hardware token to an agent, with constraints on it's use.
143///
144/// This structure is sent in a [`Request::AddSmartcardKeyConstrained`](crate::proto::Request::AddSmartcardKeyConstrained) (`SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED`) message.
145///
146/// This is a variant of [`Request::AddSmartcardKey`](crate::proto::Request::AddSmartcardKey) with a set of [`KeyConstraint`]s attached.
147///
148/// Described in [draft-miller-ssh-agent-14 § 3.2.6](https://www.ietf.org/archive/id/draft-miller-ssh-agent-14.html#section-3.2.6)
149#[derive(Clone, PartialEq, Debug)]
150pub struct AddSmartcardKeyConstrained {
151    /// A key stored on a hardware token.
152    pub key: SmartcardKey,
153
154    /// Constraints to be placed on the `key`.
155    pub constraints: Vec<KeyConstraint>,
156}
157
158impl Decode for AddSmartcardKeyConstrained {
159    type Error = Error;
160
161    fn decode(reader: &mut impl Reader) -> Result<Self> {
162        let key = SmartcardKey::decode(reader)?;
163        let mut constraints = vec![];
164
165        while !reader.is_finished() {
166            constraints.push(KeyConstraint::decode(reader)?);
167        }
168        Ok(Self { key, constraints })
169    }
170}
171
172impl Encode for AddSmartcardKeyConstrained {
173    fn encoded_len(&self) -> ssh_encoding::Result<usize> {
174        self.constraints
175            .iter()
176            .try_fold(self.key.encoded_len()?, |acc, e| {
177                let constraint_len = e.encoded_len()?;
178                usize::checked_add(acc, constraint_len).ok_or(ssh_encoding::Error::Length)
179            })
180    }
181
182    fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
183        self.key.encode(writer)?;
184        for constraint in &self.constraints {
185            constraint.encode(writer)?;
186        }
187        Ok(())
188    }
189}