-
Notifications
You must be signed in to change notification settings - Fork 6
Add constraint parsing to RusticaAgent #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,42 @@ | ||||||||||||||||||||
| use sshcerts::ssh::Reader; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| use crate::sshagent::error::ParsingError; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| #[derive(Debug)] | ||||||||||||||||||||
| pub enum Constraint { | ||||||||||||||||||||
| Lifetime(u32), | ||||||||||||||||||||
| Confirm, | ||||||||||||||||||||
| Extension(String, Vec<u8>), | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| pub fn parse_constraints(buf: &[u8]) -> ParsingError<Vec<Constraint>> { | ||||||||||||||||||||
| let mut constraints = Vec::new(); | ||||||||||||||||||||
| let mut reader = Reader::new(buf); | ||||||||||||||||||||
| let total_bytes = buf.len(); | ||||||||||||||||||||
| while reader.get_offset() < total_bytes { | ||||||||||||||||||||
| let constraint_type = reader | ||||||||||||||||||||
| .read_raw_bytes(1) | ||||||||||||||||||||
| .map_err(|_| "Failed to read constraint type")?[0]; | ||||||||||||||||||||
| match constraint_type { | ||||||||||||||||||||
| 1 => { | ||||||||||||||||||||
| constraints.push(Constraint::Lifetime( | ||||||||||||||||||||
| reader | ||||||||||||||||||||
| .read_u32() | ||||||||||||||||||||
| .map_err(|_| "Failed to read u32 for lifetime")?, | ||||||||||||||||||||
| )); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| 2 => constraints.push(Constraint::Confirm), | ||||||||||||||||||||
| 255 => { | ||||||||||||||||||||
| let ext_name = reader | ||||||||||||||||||||
| .read_string() | ||||||||||||||||||||
| .map_err(|_| "Failed to read string for extension name")?; | ||||||||||||||||||||
| let ext_data = reader | ||||||||||||||||||||
| .read_bytes() | ||||||||||||||||||||
| .map_err(|_| "Failed to read bytes for extension data")?; | ||||||||||||||||||||
| constraints.push(Constraint::Extension(ext_name, ext_data)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| _ => return Err("Unknown constraint type".into()), | ||||||||||||||||||||
|
||||||||||||||||||||
| _ => return Err("Unknown constraint type".into()), | |
| _ => { | |
| let offset = reader.get_offset().saturating_sub(1); | |
| return Err(format!( | |
| "Unknown constraint type {} at offset {}", | |
| constraint_type, offset | |
| ) | |
| .into()); | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,5 @@ | ||||||||||||||||
| use crate::sshagent::constraints::Constraint; | ||||||||||||||||
|
|
||||||||||||||||
| use super::protocol::Request; | ||||||||||||||||
| use super::protocol::Response; | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -9,6 +11,11 @@ use sshcerts::PrivateKey; | |||||||||||||||
| #[async_trait] | ||||||||||||||||
| pub trait SshAgentHandler: Send + Sync { | ||||||||||||||||
| async fn add_identity(&self, key: PrivateKey) -> HandleResult<Response>; | ||||||||||||||||
| async fn add_identity_constrained( | ||||||||||||||||
| &self, | ||||||||||||||||
| key: PrivateKey, | ||||||||||||||||
| constraints: Vec<Constraint>, | ||||||||||||||||
| ) -> HandleResult<Response>; | ||||||||||||||||
|
Comment on lines
+17
to
+18
|
||||||||||||||||
| constraints: Vec<Constraint>, | |
| ) -> HandleResult<Response>; | |
| _constraints: Vec<Constraint>, | |
| ) -> HandleResult<Response> { | |
| // Default behavior: ignore constraints and treat this as a regular identity add. | |
| self.add_identity(key).await | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,12 @@ | ||
| extern crate byteorder; | ||
|
|
||
| mod agent; | ||
| mod protocol; | ||
| mod handler; | ||
| pub mod constraints; | ||
| pub mod error; | ||
| mod handler; | ||
| mod protocol; | ||
|
Comment on lines
3
to
+7
|
||
|
|
||
| pub use handler::SshAgentHandler; | ||
| pub use agent::Agent; | ||
| pub use handler::SshAgentHandler; | ||
| pub use protocol::Identity; | ||
| pub use protocol::Response; | ||
| pub use protocol::Identity; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add_identity_constrainedcurrently accepts and stores the key even whenconstraintsis non-empty, effectively ignoring lifetime/confirm/extension constraints. This can widen key usage vs what the client requested (e.g., confirm-required or limited lifetime), which is a security footgun. Either enforce the supported constraints here (or store them alongside the key for later enforcement), or reject non-empty constraints until enforcement is implemented (returnResponse::Failure).