1use crate::agent::AgentHandle;
7use crate::error::AgentError as AuthsAgentError;
8use log::{debug, error, warn};
9#[cfg(unix)]
10use ssh_agent_lib::agent::Agent;
11use ssh_agent_lib::agent::Session;
12use ssh_agent_lib::error::AgentError as SSHAgentError;
13use ssh_agent_lib::proto::{AddIdentity, Credential, Identity, RemoveIdentity, SignRequest};
14use ssh_key::private::KeypairData;
15use ssh_key::public::{Ed25519PublicKey, KeyData};
16use ssh_key::{Algorithm, Signature};
17use std::convert::TryInto;
18use std::io;
19use std::sync::Arc;
20use zeroize::Zeroizing;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub struct PeerIdentity {
33 pub uid: u32,
35 pub pid: Option<i32>,
38}
39
40impl PeerIdentity {
41 pub fn local() -> Self {
44 Self { uid: 0, pid: None }
45 }
46}
47
48pub trait SignAuthorizer: Send + Sync {
55 fn authorize_sign(&self, peer: &PeerIdentity) -> bool;
60}
61
62pub struct AllowAllSigning;
70
71impl SignAuthorizer for AllowAllSigning {
72 fn authorize_sign(&self, _peer: &PeerIdentity) -> bool {
73 true
74 }
75}
76
77pub struct PerCallerAuthorizer {
92 approve: Box<dyn Fn(&PeerIdentity) -> bool + Send + Sync>,
93 approved: std::sync::Mutex<std::collections::HashSet<(u32, Option<i32>)>>,
94}
95
96impl PerCallerAuthorizer {
97 pub fn new(approve: impl Fn(&PeerIdentity) -> bool + Send + Sync + 'static) -> Self {
103 Self {
104 approve: Box::new(approve),
105 approved: std::sync::Mutex::new(std::collections::HashSet::new()),
106 }
107 }
108}
109
110impl SignAuthorizer for PerCallerAuthorizer {
111 fn authorize_sign(&self, peer: &PeerIdentity) -> bool {
112 let key = (peer.uid, peer.pid);
113 let mut approved = self
114 .approved
115 .lock()
116 .unwrap_or_else(|poisoned| poisoned.into_inner());
117 if approved.contains(&key) {
118 return true;
119 }
120 if (self.approve)(peer) {
121 approved.insert(key);
122 true
123 } else {
124 false
125 }
126 }
127}
128
129#[derive(Clone)]
134pub struct AgentSession {
135 handle: Arc<AgentHandle>,
137 peer: PeerIdentity,
139 authorizer: Arc<dyn SignAuthorizer>,
141}
142
143impl AgentSession {
144 pub fn new(handle: Arc<AgentHandle>) -> Self {
149 Self {
150 handle,
151 peer: PeerIdentity::local(),
152 authorizer: Arc::new(AllowAllSigning),
153 }
154 }
155
156 pub fn with_authorizer(
163 handle: Arc<AgentHandle>,
164 peer: PeerIdentity,
165 authorizer: Arc<dyn SignAuthorizer>,
166 ) -> Self {
167 Self {
168 handle,
169 peer,
170 authorizer,
171 }
172 }
173
174 pub fn handle(&self) -> &AgentHandle {
176 &self.handle
177 }
178}
179
180impl std::fmt::Debug for AgentSession {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 f.debug_struct("AgentSession")
183 .field("socket_path", self.handle.socket_path())
184 .field("is_running", &self.handle.is_running())
185 .finish()
186 }
187}
188
189pub use auths_crypto::build_ed25519_pkcs8_v2;
194
195#[ssh_agent_lib::async_trait]
196impl Session for AgentSession {
197 async fn request_identities(&mut self) -> Result<Vec<Identity>, SSHAgentError> {
198 let core = self.handle.lock().map_err(|_| {
199 error!("AgentSession failed to lock agent core (mutex poisoned).");
200 SSHAgentError::Failure
201 })?;
202
203 let pubkey_byte_vectors = core.public_keys();
204 debug!(
205 "request_identities: Agent core has {} keys.",
206 pubkey_byte_vectors.len()
207 );
208
209 let identities = pubkey_byte_vectors
210 .into_iter()
211 .filter_map(|pubkey_bytes| {
212 let key_data = match pubkey_bytes.len() {
213 32 => {
214 let key_bytes_array: &[u8; 32] = pubkey_bytes.as_slice().try_into().ok()?;
215 KeyData::Ed25519(Ed25519PublicKey(*key_bytes_array))
216 }
217 33 | 65 => {
218 let ecdsa_pk =
220 ssh_key::public::EcdsaPublicKey::from_sec1_bytes(&pubkey_bytes).ok()?;
221 KeyData::Ecdsa(ecdsa_pk)
222 }
223 n => {
224 warn!("request_identities: unsupported key length ({n}). Skipping.");
225 return None;
226 }
227 };
228 let comment = format!(
229 "auths-key-{}",
230 hex::encode(&pubkey_bytes[..4.min(pubkey_bytes.len())])
231 );
232 debug!("Adding identity with comment: {}", comment);
233 Some(Identity {
234 pubkey: key_data,
235 comment,
236 })
237 })
238 .collect();
239
240 Ok(identities)
241 }
242
243 async fn sign(&mut self, request: SignRequest) -> Result<Signature, SSHAgentError> {
244 if !self.authorizer.authorize_sign(&self.peer) {
245 warn!(
246 "Sign request refused: peer not authorized to sign (uid={}, pid={:?})",
247 self.peer.uid, self.peer.pid
248 );
249 return Err(SSHAgentError::Failure);
250 }
251
252 debug!(
253 "Handling sign request for key type: {:?}",
254 request.pubkey.algorithm()
255 );
256
257 let (pubkey_bytes_to_sign_with, algorithm) = match &request.pubkey {
258 KeyData::Ed25519(key) => (key.as_ref().to_vec(), Algorithm::Ed25519),
259 KeyData::Ecdsa(ecdsa_key) => {
260 let point_bytes = ecdsa_key.as_ref();
261 (
262 point_bytes.to_vec(),
263 Algorithm::Ecdsa {
264 curve: ssh_key::EcdsaCurve::NistP256,
265 },
266 )
267 }
268 other_key_type => {
269 let err_msg = format!(
270 "Unsupported key type requested for signing: {:?}",
271 other_key_type.algorithm()
272 );
273 error!("{}", err_msg);
274 return Err(SSHAgentError::other(io::Error::new(
275 io::ErrorKind::Unsupported,
276 err_msg,
277 )));
278 }
279 };
280
281 match self.handle.sign(&pubkey_bytes_to_sign_with, &request.data) {
282 Ok(signature_bytes) => {
283 debug!("Successfully signed data using agent core.");
284 Signature::new(algorithm, signature_bytes).map_err(|e| {
285 let err_msg = format!(
286 "Internal error: Failed to create ssh_key::Signature from core signature: {}",
287 e
288 );
289 error!("{}", err_msg);
290 SSHAgentError::other(io::Error::new(io::ErrorKind::InvalidData, err_msg))
291 })
292 }
293 Err(AuthsAgentError::KeyNotFound) => {
294 warn!("Sign request failed: Key not found in agent core.");
295 Err(SSHAgentError::Failure)
296 }
297 Err(AuthsAgentError::AgentLocked) => {
298 warn!("Sign request refused: agent is locked.");
299 Err(SSHAgentError::Failure)
300 }
301 Err(other_core_error) => {
302 let err_msg = format!("Agent core signing error: {}", other_core_error);
303 error!("{}", err_msg);
304 Err(SSHAgentError::other(io::Error::other(err_msg)))
305 }
306 }
307 }
308
309 async fn add_identity(&mut self, identity: AddIdentity) -> Result<(), SSHAgentError> {
310 debug!("Handling add_identity request");
311
312 let pkcs8_bytes: Zeroizing<Vec<u8>> = match &identity.credential {
313 Credential::Key { privkey, .. } => match privkey {
314 KeypairData::Ed25519(kp) => {
315 let seed = kp.private.to_bytes();
316 let pubkey = kp.public.0;
317 build_ed25519_pkcs8_v2(&seed, &pubkey)
318 }
319 KeypairData::Ecdsa(ssh_key::private::EcdsaKeypair::NistP256 {
320 private, ..
321 }) => {
322 use auths_crypto::{TypedSeed, TypedSignerKey};
323 let scalar_bytes = private.as_slice();
324 if scalar_bytes.len() != 32 {
325 let err_msg = format!(
326 "Invalid P-256 scalar length: expected 32, got {}",
327 scalar_bytes.len()
328 );
329 error!("{}", err_msg);
330 return Err(SSHAgentError::other(io::Error::new(
331 io::ErrorKind::InvalidData,
332 err_msg,
333 )));
334 }
335 #[allow(clippy::expect_used)] let mut scalar = [0u8; 32];
337 scalar.copy_from_slice(scalar_bytes);
338 let signer =
339 TypedSignerKey::from_seed(TypedSeed::P256(scalar)).map_err(|e| {
340 let err_msg = format!("P-256 signer construction failed: {}", e);
341 error!("{}", err_msg);
342 SSHAgentError::other(io::Error::other(err_msg))
343 })?;
344 let pkcs8 = signer.to_pkcs8().map_err(|e| {
345 let err_msg = format!("P-256 PKCS8 encoding failed: {}", e);
346 error!("{}", err_msg);
347 SSHAgentError::other(io::Error::other(err_msg))
348 })?;
349 Zeroizing::new(pkcs8.as_ref().to_vec())
350 }
351 other => {
352 let err_msg = format!(
353 "Unsupported key type for add_identity: {:?}",
354 other.algorithm()
355 );
356 error!("{}", err_msg);
357 return Err(SSHAgentError::other(io::Error::new(
358 io::ErrorKind::Unsupported,
359 err_msg,
360 )));
361 }
362 },
363 Credential::Cert { .. } => {
364 error!("Certificate credentials are not supported for add_identity");
365 return Err(SSHAgentError::other(io::Error::new(
366 io::ErrorKind::Unsupported,
367 "Certificate credentials are not supported",
368 )));
369 }
370 };
371
372 self.handle.register_key(pkcs8_bytes).map_err(|e| {
373 let err_msg = format!("Failed to register key in agent: {}", e);
374 error!("{}", err_msg);
375 SSHAgentError::other(io::Error::other(err_msg))
376 })?;
377
378 debug!("Successfully added identity to agent");
379 Ok(())
380 }
381
382 async fn remove_identity(&mut self, identity: RemoveIdentity) -> Result<(), SSHAgentError> {
383 debug!("Handling remove_identity request");
384
385 let pubkey_bytes = match &identity.pubkey {
386 KeyData::Ed25519(key) => key.as_ref().to_vec(),
387 KeyData::Ecdsa(key) => key.as_ref().to_vec(),
388 other => {
389 let err_msg = format!(
390 "Unsupported key type for remove_identity: {:?}",
391 other.algorithm()
392 );
393 error!("{}", err_msg);
394 return Err(SSHAgentError::other(io::Error::new(
395 io::ErrorKind::Unsupported,
396 err_msg,
397 )));
398 }
399 };
400
401 let mut core = self.handle.lock().map_err(|_| {
402 error!("AgentSession failed to lock agent core (mutex poisoned).");
403 SSHAgentError::Failure
404 })?;
405
406 core.unregister_key(&pubkey_bytes).map_err(|e| {
407 let err_msg = format!("Failed to remove key from agent: {}", e);
408 error!("{}", err_msg);
409 SSHAgentError::other(io::Error::new(io::ErrorKind::NotFound, err_msg))
410 })?;
411
412 debug!("Successfully removed identity from agent");
413 Ok(())
414 }
415
416 async fn remove_all_identities(&mut self) -> Result<(), SSHAgentError> {
417 debug!("Handling remove_all_identities request");
418
419 let mut core = self.handle.lock().map_err(|_| {
420 error!("AgentSession failed to lock agent core (mutex poisoned).");
421 SSHAgentError::Failure
422 })?;
423
424 core.clear_keys();
425 debug!("Successfully removed all identities from agent");
426 Ok(())
427 }
428}
429
430fn peer_is_authorized(peer_uid: u32, owner_uid: u32) -> bool {
444 peer_uid == owner_uid
445}
446
447#[cfg(unix)]
452pub(crate) enum MaybeAuthorized {
453 Authorized(AgentSession),
455 Denied,
457}
458
459#[cfg(unix)]
460#[ssh_agent_lib::async_trait]
461impl Session for MaybeAuthorized {
462 async fn request_identities(&mut self) -> Result<Vec<Identity>, SSHAgentError> {
463 match self {
464 Self::Authorized(session) => session.request_identities().await,
465 Self::Denied => Err(SSHAgentError::Failure),
466 }
467 }
468
469 async fn sign(&mut self, request: SignRequest) -> Result<Signature, SSHAgentError> {
470 match self {
471 Self::Authorized(session) => session.sign(request).await,
472 Self::Denied => Err(SSHAgentError::Failure),
473 }
474 }
475
476 async fn add_identity(&mut self, identity: AddIdentity) -> Result<(), SSHAgentError> {
477 match self {
478 Self::Authorized(session) => session.add_identity(identity).await,
479 Self::Denied => Err(SSHAgentError::Failure),
480 }
481 }
482
483 async fn remove_identity(&mut self, identity: RemoveIdentity) -> Result<(), SSHAgentError> {
484 match self {
485 Self::Authorized(session) => session.remove_identity(identity).await,
486 Self::Denied => Err(SSHAgentError::Failure),
487 }
488 }
489
490 async fn remove_all_identities(&mut self) -> Result<(), SSHAgentError> {
491 match self {
492 Self::Authorized(session) => session.remove_all_identities().await,
493 Self::Denied => Err(SSHAgentError::Failure),
494 }
495 }
496}
497
498#[cfg(unix)]
506pub(crate) struct PeerAuthorizedAgent {
507 handle: Arc<AgentHandle>,
508 owner_uid: u32,
509 authorizer: Arc<dyn SignAuthorizer>,
510}
511
512#[cfg(unix)]
513impl PeerAuthorizedAgent {
514 pub(crate) fn new(
525 handle: Arc<AgentHandle>,
526 owner_uid: u32,
527 authorizer: Arc<dyn SignAuthorizer>,
528 ) -> Self {
529 Self {
530 handle,
531 owner_uid,
532 authorizer,
533 }
534 }
535}
536
537#[cfg(unix)]
538impl Agent<tokio::net::UnixListener> for PeerAuthorizedAgent {
539 fn new_session(&mut self, socket: &tokio::net::UnixStream) -> impl Session {
540 match socket.peer_cred() {
541 Ok(cred) if peer_is_authorized(cred.uid(), self.owner_uid) => {
542 let peer = PeerIdentity {
543 uid: cred.uid(),
544 pid: cred.pid(),
545 };
546 MaybeAuthorized::Authorized(AgentSession::with_authorizer(
547 self.handle.clone(),
548 peer,
549 self.authorizer.clone(),
550 ))
551 }
552 Ok(cred) => {
553 warn!(
554 "Refusing agent connection from uid {} (agent owner uid {})",
555 cred.uid(),
556 self.owner_uid
557 );
558 MaybeAuthorized::Denied
559 }
560 Err(e) => {
561 error!("Refusing agent connection: cannot read peer credentials: {e}");
562 MaybeAuthorized::Denied
563 }
564 }
565 }
566}
567
568#[cfg(test)]
569mod tests {
570 use super::*;
571 use ring::rand::SystemRandom;
572 use ring::signature::Ed25519KeyPair;
573 use ssh_key::private::Ed25519Keypair as SshEd25519Keypair;
574 use std::path::PathBuf;
575 use zeroize::Zeroizing;
576
577 fn generate_test_pkcs8() -> Vec<u8> {
578 let rng = SystemRandom::new();
579 let pkcs8_doc = Ed25519KeyPair::generate_pkcs8(&rng).expect("Failed to generate PKCS#8");
580 pkcs8_doc.as_ref().to_vec()
581 }
582
583 #[tokio::test]
587 async fn agent_refuses_to_sign_when_authorizer_denies() {
588 struct DenyAll;
589 impl SignAuthorizer for DenyAll {
590 fn authorize_sign(&self, _peer: &PeerIdentity) -> bool {
591 false
592 }
593 }
594
595 let seed: [u8; 32] = {
596 let pkcs8 = generate_test_pkcs8();
597 let mut s = [0u8; 32];
598 s.copy_from_slice(&pkcs8[16..48]);
599 s
600 };
601 let ssh_keypair = SshEd25519Keypair::from_seed(&seed);
602 let pubkey_bytes = ssh_keypair.public.0;
603
604 let handle = Arc::new(AgentHandle::new(PathBuf::from("/tmp/test-deny.sock")));
605 let peer = PeerIdentity {
606 uid: 1000,
607 pid: Some(4242),
608 };
609 let mut session = AgentSession::with_authorizer(handle.clone(), peer, Arc::new(DenyAll));
610
611 session
612 .add_identity(AddIdentity {
613 credential: Credential::Key {
614 privkey: KeypairData::Ed25519(ssh_keypair),
615 comment: "test-key".to_string(),
616 },
617 })
618 .await
619 .unwrap();
620 assert_eq!(handle.key_count().unwrap(), 1);
621
622 let request = SignRequest {
623 pubkey: KeyData::Ed25519(Ed25519PublicKey(pubkey_bytes)),
624 data: b"unauthorized payload".to_vec(),
625 flags: 0,
626 };
627 let result = session.sign(request).await;
628 assert!(
629 result.is_err(),
630 "a denied peer must not obtain a signature even when the agent is unlocked and the key is present"
631 );
632 }
633
634 #[test]
637 fn per_caller_pins_approved_and_reprompts_a_different_process() {
638 use std::sync::atomic::{AtomicUsize, Ordering};
639
640 let prompts = Arc::new(AtomicUsize::new(0));
641 let p = prompts.clone();
642 let auth = PerCallerAuthorizer::new(move |peer: &PeerIdentity| {
643 p.fetch_add(1, Ordering::SeqCst);
644 peer.uid == 1000 });
646
647 let git = PeerIdentity {
648 uid: 1000,
649 pid: Some(11),
650 };
651 let malware = PeerIdentity {
652 uid: 1000,
653 pid: Some(22),
654 };
655 let stranger = PeerIdentity {
656 uid: 1001,
657 pid: Some(33),
658 };
659
660 assert!(
661 auth.authorize_sign(&git),
662 "first request from git is approved"
663 );
664 assert!(auth.authorize_sign(&git), "git is now pinned");
665 assert_eq!(
666 prompts.load(Ordering::SeqCst),
667 1,
668 "an approved caller is not re-prompted per signature"
669 );
670
671 assert!(
672 !auth.authorize_sign(&stranger),
673 "an unapproved peer is refused"
674 );
675 assert!(
676 auth.authorize_sign(&malware),
677 "a different same-uid process is approved by this fn, but only via its own prompt"
678 );
679 assert_eq!(
680 prompts.load(Ordering::SeqCst),
681 3,
682 "a different process triggers a fresh approval; pinning is per-(uid,pid)"
683 );
684 }
685
686 #[test]
687 fn test_agent_session_new() {
688 let handle = Arc::new(AgentHandle::new(PathBuf::from("/tmp/test.sock")));
689 let session = AgentSession::new(handle.clone());
690
691 assert_eq!(
692 session.handle().socket_path(),
693 &PathBuf::from("/tmp/test.sock")
694 );
695 }
696
697 #[test]
698 fn test_agent_session_clone_shares_handle() {
699 let handle = Arc::new(AgentHandle::new(PathBuf::from("/tmp/test.sock")));
700
701 let pkcs8_bytes = generate_test_pkcs8();
702 handle
703 .register_key(Zeroizing::new(pkcs8_bytes))
704 .expect("Failed to register key");
705
706 let session1 = AgentSession::new(handle.clone());
707 let session2 = session1.clone();
708
709 assert_eq!(session1.handle().key_count().unwrap(), 1);
711 assert_eq!(session2.handle().key_count().unwrap(), 1);
712 }
713
714 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
715 async fn test_add_identity_round_trip() {
716 let seed: [u8; 32] = {
718 let pkcs8 = generate_test_pkcs8();
719 let mut s = [0u8; 32];
721 s.copy_from_slice(&pkcs8[16..48]);
722 s
723 };
724
725 let ssh_keypair = SshEd25519Keypair::from_seed(&seed);
726 let pubkey_bytes = ssh_keypair.public.0;
727
728 let identity = AddIdentity {
730 credential: Credential::Key {
731 privkey: KeypairData::Ed25519(ssh_keypair),
732 comment: "test-key".to_string(),
733 },
734 };
735
736 let handle = Arc::new(AgentHandle::new(PathBuf::from("/tmp/test-add.sock")));
738 let mut session = AgentSession::new(handle.clone());
739
740 assert_eq!(handle.key_count().unwrap(), 0);
742
743 session.add_identity(identity).await.unwrap();
745
746 assert_eq!(handle.key_count().unwrap(), 1);
748
749 let sign_request = SignRequest {
751 pubkey: KeyData::Ed25519(Ed25519PublicKey(pubkey_bytes)),
752 data: b"test data for signing".to_vec(),
753 flags: 0,
754 };
755
756 let signature = session.sign(sign_request).await.unwrap();
757 assert_eq!(signature.algorithm(), Algorithm::Ed25519);
758 assert!(!signature.as_bytes().is_empty());
759
760 let ring_pubkey =
762 ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, &pubkey_bytes);
763 ring_pubkey
764 .verify(b"test data for signing", signature.as_bytes())
765 .expect("Signature verification failed");
766 }
767
768 #[tokio::test]
769 async fn test_remove_identity() {
770 let handle = Arc::new(AgentHandle::new(PathBuf::from("/tmp/test-rm.sock")));
771 let mut session = AgentSession::new(handle.clone());
772
773 let seed: [u8; 32] = {
775 let pkcs8 = generate_test_pkcs8();
776 let mut s = [0u8; 32];
777 s.copy_from_slice(&pkcs8[16..48]);
778 s
779 };
780 let ssh_keypair = SshEd25519Keypair::from_seed(&seed);
781 let pubkey_bytes = ssh_keypair.public.0;
782
783 let identity = AddIdentity {
784 credential: Credential::Key {
785 privkey: KeypairData::Ed25519(ssh_keypair),
786 comment: "test-key".to_string(),
787 },
788 };
789 session.add_identity(identity).await.unwrap();
790 assert_eq!(handle.key_count().unwrap(), 1);
791
792 let remove = RemoveIdentity {
794 pubkey: KeyData::Ed25519(Ed25519PublicKey(pubkey_bytes)),
795 };
796 session.remove_identity(remove).await.unwrap();
797 assert_eq!(handle.key_count().unwrap(), 0);
798 }
799
800 #[tokio::test]
801 async fn test_remove_all_identities() {
802 let handle = Arc::new(AgentHandle::new(PathBuf::from("/tmp/test-rmall.sock")));
803 let mut session = AgentSession::new(handle.clone());
804
805 for _ in 0..2 {
807 let seed: [u8; 32] = {
808 let pkcs8 = generate_test_pkcs8();
809 let mut s = [0u8; 32];
810 s.copy_from_slice(&pkcs8[16..48]);
811 s
812 };
813 let ssh_keypair = SshEd25519Keypair::from_seed(&seed);
814 let identity = AddIdentity {
815 credential: Credential::Key {
816 privkey: KeypairData::Ed25519(ssh_keypair),
817 comment: "test-key".to_string(),
818 },
819 };
820 session.add_identity(identity).await.unwrap();
821 }
822 assert_eq!(handle.key_count().unwrap(), 2);
823
824 session.remove_all_identities().await.unwrap();
826 assert_eq!(handle.key_count().unwrap(), 0);
827 }
828
829 #[cfg(unix)]
830 fn registered_sign_request(handle: &Arc<AgentHandle>) -> SignRequest {
831 let pkcs8 = generate_test_pkcs8();
832 handle
833 .register_key(Zeroizing::new(pkcs8))
834 .expect("register key");
835 let pubkeys = handle.public_keys().expect("public keys");
836 let pubkey_bytes: [u8; 32] = pubkeys[0]
837 .as_slice()
838 .try_into()
839 .expect("ed25519 pubkey is 32 bytes");
840 SignRequest {
841 pubkey: KeyData::Ed25519(Ed25519PublicKey(pubkey_bytes)),
842 data: b"data to sign".to_vec(),
843 flags: 0,
844 }
845 }
846
847 #[test]
848 fn peer_authorized_only_for_matching_uid() {
849 assert!(peer_is_authorized(1000, 1000));
850 assert!(!peer_is_authorized(1001, 1000));
851 assert!(!peer_is_authorized(0, 1000));
852 }
853
854 #[cfg(unix)]
855 #[tokio::test]
856 async fn new_session_serves_owner_uid() {
857 use tokio::net::UnixStream;
858 let (a, _b) = UnixStream::pair().expect("socketpair");
859 let owner_uid = a.peer_cred().expect("peer cred").uid();
860
861 let handle = Arc::new(AgentHandle::new(PathBuf::from("/tmp/peer-allow.sock")));
862 let request = registered_sign_request(&handle);
863
864 let mut agent = PeerAuthorizedAgent::new(handle, owner_uid, Arc::new(AllowAllSigning));
865 let mut session = agent.new_session(&a);
866 assert!(
867 session.sign(request).await.is_ok(),
868 "the owning uid must be able to sign"
869 );
870 }
871
872 #[cfg(unix)]
873 #[tokio::test]
874 async fn new_session_denies_foreign_uid() {
875 use tokio::net::UnixStream;
876 let (a, _b) = UnixStream::pair().expect("socketpair");
877 let peer_uid = a.peer_cred().expect("peer cred").uid();
878
879 let handle = Arc::new(AgentHandle::new(PathBuf::from("/tmp/peer-deny.sock")));
880 let request = registered_sign_request(&handle);
881
882 let mut agent =
885 PeerAuthorizedAgent::new(handle, peer_uid.wrapping_add(1), Arc::new(AllowAllSigning));
886 let mut session = agent.new_session(&a);
887 assert!(
888 session.sign(request).await.is_err(),
889 "a foreign-uid peer must not be able to sign"
890 );
891 }
892
893 #[cfg(unix)]
894 #[tokio::test]
895 async fn denied_session_refuses_every_request() {
896 let mut session = MaybeAuthorized::Denied;
897 assert!(session.request_identities().await.is_err());
898 let request = SignRequest {
899 pubkey: KeyData::Ed25519(Ed25519PublicKey([7u8; 32])),
900 data: b"x".to_vec(),
901 flags: 0,
902 };
903 assert!(session.sign(request).await.is_err());
904 }
905
906 #[cfg(unix)]
907 #[tokio::test]
908 async fn denied_session_refuses_every_request_variant() {
909 use ssh_agent_lib::proto::{
910 AddIdentityConstrained, AddSmartcardKeyConstrained, Extension, Request, Response,
911 SmartcardKey,
912 };
913
914 fn ed_keydata() -> KeyData {
915 KeyData::Ed25519(Ed25519PublicKey([0u8; 32]))
916 }
917 fn an_add_identity() -> AddIdentity {
918 AddIdentity {
919 credential: Credential::Key {
920 privkey: KeypairData::Ed25519(SshEd25519Keypair::from_seed(&[0u8; 32])),
921 comment: String::new(),
922 },
923 }
924 }
925 fn a_smartcard() -> SmartcardKey {
926 SmartcardKey {
927 id: String::new(),
928 pin: String::new().into(),
929 }
930 }
931
932 let requests = vec![
936 Request::RequestIdentities,
937 Request::SignRequest(SignRequest {
938 pubkey: ed_keydata(),
939 data: Vec::new(),
940 flags: 0,
941 }),
942 Request::AddIdentity(an_add_identity()),
943 Request::RemoveIdentity(RemoveIdentity {
944 pubkey: ed_keydata(),
945 }),
946 Request::RemoveAllIdentities,
947 Request::AddSmartcardKey(a_smartcard()),
948 Request::RemoveSmartcardKey(a_smartcard()),
949 Request::Lock(String::new()),
950 Request::Unlock(String::new()),
951 Request::AddIdConstrained(AddIdentityConstrained {
952 identity: an_add_identity(),
953 constraints: Vec::new(),
954 }),
955 Request::AddSmartcardKeyConstrained(AddSmartcardKeyConstrained {
956 key: a_smartcard(),
957 constraints: Vec::new(),
958 }),
959 Request::Extension(Extension {
960 name: String::new(),
961 details: Vec::<u8>::new().into(),
962 }),
963 ];
964 assert_eq!(requests.len(), 12, "all agent request variants are covered");
965
966 for request in requests {
967 let mut session = MaybeAuthorized::Denied;
968 let response = match session.handle(request).await {
970 Err(SSHAgentError::ExtensionFailure) => Response::ExtensionFailure,
971 Err(_) => Response::Failure,
972 Ok(r) => r,
973 };
974 assert!(
975 matches!(response, Response::Failure | Response::ExtensionFailure),
976 "a denied session must refuse every request variant, got {response:?}"
977 );
978 }
979 }
980
981 #[cfg(unix)]
982 #[tokio::test]
983 async fn sign_resets_idle_timer() {
984 use std::time::Duration;
985 let handle = Arc::new(AgentHandle::with_timeout(
986 PathBuf::from("/tmp/idle-touch.sock"),
987 Duration::from_millis(300),
988 ));
989 let request = registered_sign_request(&handle);
990 let mut session = AgentSession::new(handle.clone());
991
992 tokio::time::sleep(Duration::from_millis(200)).await;
993 session.sign(request).await.expect("sign should succeed");
994 tokio::time::sleep(Duration::from_millis(200)).await;
995
996 assert!(
997 !handle.is_idle_timed_out(),
998 "a successful sign must reset the idle timer so the agent stays unlocked"
999 );
1000 }
1001}