diff --git a/Cargo.lock b/Cargo.lock index 20bc023..68be9ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4779,9 +4779,9 @@ dependencies = [ [[package]] name = "sshcerts" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86255551f89d85d725a8aa6c795e87f582c4a152563defec247f76600416ee" +checksum = "77fb95516e393037486aaa0dddf65fd6ada488d274fb3914204706dea7c60b32" dependencies = [ "aes 0.7.5", "authenticator", diff --git a/rustica-agent/Cargo.toml b/rustica-agent/Cargo.toml index acabe0b..90311df 100644 --- a/rustica-agent/Cargo.toml +++ b/rustica-agent/Cargo.toml @@ -24,7 +24,7 @@ serde = "1.0.97" serde_derive = "1.0" sha2 = "0.9.2" # For Production -sshcerts = { version = "0.14.0" } +sshcerts = { version = "0.14.1" } # For Development # sshcerts = { path = "../../sshcerts", features = [ # "yubikey-support", diff --git a/rustica-agent/src/lib.rs b/rustica-agent/src/lib.rs index f8b08fd..99052c5 100644 --- a/rustica-agent/src/lib.rs +++ b/rustica-agent/src/lib.rs @@ -10,6 +10,7 @@ use async_trait::async_trait; use rustica::key::U2FAttestation; use config::{Options, UpdatableConfiguration}; +use sshagent::constraints::Constraint; pub use config::Config; use serde_derive::{Deserialize, Serialize}; @@ -301,6 +302,20 @@ impl SshAgentHandler for Handler { Ok(Response::Success) } + async fn add_identity_constrained( + &self, + private_key: PrivateKey, + constraints: Vec, + ) -> Result { + trace!("Add Identity Constrained call"); + if !constraints.is_empty() { + trace!("Key is being added with constraints"); + } + let public_key = private_key.pubkey.encode(); + self.identities.lock().await.insert(public_key, private_key); + Ok(Response::Success) + } + async fn identities(&self) -> Result { trace!("Identities call"); // We start building identies with the manually loaded keys diff --git a/rustica-agent/src/sshagent/constraints.rs b/rustica-agent/src/sshagent/constraints.rs new file mode 100644 index 0000000..e798ffd --- /dev/null +++ b/rustica-agent/src/sshagent/constraints.rs @@ -0,0 +1,42 @@ +use sshcerts::ssh::Reader; + +use crate::sshagent::error::ParsingError; + +#[derive(Debug)] +pub enum Constraint { + Lifetime(u32), + Confirm, + Extension(String, Vec), +} + +pub fn parse_constraints(buf: &[u8]) -> ParsingError> { + 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()), + } + } + Ok(constraints) +} diff --git a/rustica-agent/src/sshagent/handler.rs b/rustica-agent/src/sshagent/handler.rs index d36a3fe..c573409 100644 --- a/rustica-agent/src/sshagent/handler.rs +++ b/rustica-agent/src/sshagent/handler.rs @@ -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; + async fn add_identity_constrained( + &self, + key: PrivateKey, + constraints: Vec, + ) -> HandleResult; async fn identities(&self) -> HandleResult; async fn sign_request( &self, @@ -29,6 +36,13 @@ pub trait SshAgentHandler: Send + Sync { .await } Request::AddIdentity { private_key } => self.add_identity(private_key).await, + Request::AddIdentityConstrained { + private_key, + constraints, + } => { + self.add_identity_constrained(private_key, constraints) + .await + } Request::Unknown => Ok(Response::Failure), } } diff --git a/rustica-agent/src/sshagent/mod.rs b/rustica-agent/src/sshagent/mod.rs index d061a88..1064a41 100644 --- a/rustica-agent/src/sshagent/mod.rs +++ b/rustica-agent/src/sshagent/mod.rs @@ -1,11 +1,12 @@ extern crate byteorder; mod agent; -mod protocol; -mod handler; +pub mod constraints; pub mod error; +mod handler; +mod protocol; -pub use handler::SshAgentHandler; pub use agent::Agent; +pub use handler::SshAgentHandler; +pub use protocol::Identity; pub use protocol::Response; -pub use protocol::Identity; \ No newline at end of file diff --git a/rustica-agent/src/sshagent/protocol.rs b/rustica-agent/src/sshagent/protocol.rs index 50c7c46..5260389 100644 --- a/rustica-agent/src/sshagent/protocol.rs +++ b/rustica-agent/src/sshagent/protocol.rs @@ -1,4 +1,5 @@ use byteorder::{BigEndian, WriteBytesExt}; +use sshcerts::ssh::Reader; use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, net::UnixStream, @@ -31,11 +32,11 @@ impl MessageRequest { 17 => MessageRequest::AddIdentity, 18 => MessageRequest::RemoveIdentity, 19 => MessageRequest::RemoveAllIdentities, - 25 => MessageRequest::AddIdConstrained, 20 => MessageRequest::AddSmartcardKey, 21 => MessageRequest::RemoveSmartcardKey, 22 => MessageRequest::Lock, 23 => MessageRequest::Unlock, + 25 => MessageRequest::AddIdConstrained, 26 => MessageRequest::AddSmartcardKeyConstrained, 27 => MessageRequest::Extension, _ => MessageRequest::Unknown, @@ -76,6 +77,10 @@ pub enum Request { AddIdentity { private_key: sshcerts::PrivateKey, }, + AddIdentityConstrained { + private_key: sshcerts::PrivateKey, + constraints: Vec, + }, Unknown, } @@ -99,7 +104,24 @@ impl Request { }, MessageRequest::RemoveIdentity => Ok(Request::Unknown), MessageRequest::RemoveAllIdentities => Ok(Request::Unknown), - MessageRequest::AddIdConstrained => Ok(Request::Unknown), + MessageRequest::AddIdConstrained => { + let mut reader = Reader::new(buf); + + let private_key = match sshcerts::PrivateKey::read_private_key(&mut reader) { + Ok(private_key) => private_key, + Err(_) => return Ok(Request::Unknown), + }; + + let constraints_buf = &buf[reader.get_offset()..]; + let constraints = match super::constraints::parse_constraints(&constraints_buf) { + Ok(constraints) => constraints, + Err(_) => return Ok(Request::Unknown), + }; + Ok(Request::AddIdentityConstrained { + private_key, + constraints, + }) + } MessageRequest::AddSmartcardKey => Ok(Request::Unknown), MessageRequest::RemoveSmartcardKey => Ok(Request::Unknown), MessageRequest::Lock => Ok(Request::Unknown), diff --git a/rustica/Cargo.toml b/rustica/Cargo.toml index d466f13..480ea14 100644 --- a/rustica/Cargo.toml +++ b/rustica/Cargo.toml @@ -41,7 +41,7 @@ serde = { version = "1.0", features = ["derive"] } # "yubikey-lite", # ] } # For Development -sshcerts = { version = "0.14.0", default-features = false, features = [ +sshcerts = { version = "0.14.1", default-features = false, features = [ "fido-lite", "x509-support", "yubikey-lite",