diff --git a/examples/ssh_agent.rs b/examples/ssh_agent.rs new file mode 100644 index 0000000..46287d1 --- /dev/null +++ b/examples/ssh_agent.rs @@ -0,0 +1,218 @@ +use ssh_agent::proto::{from_bytes, RsaPublicKey, to_bytes}; +use ssh_agent::proto::message::{self, Message, SignRequest}; +use ssh_agent::proto::signature::{self, Signature}; +use ssh_agent::proto::public_key::PublicKey; +use ssh_agent::proto::private_key::{PrivateKey, RsaPrivateKey}; +use ssh_agent::agent::Agent; + +use std::sync::RwLock; +use std::error::Error; +use std::fs::remove_file; + +use openssl::sign::Signer; +use openssl::rsa::Rsa; +use openssl::pkey::PKey; +use openssl::hash::MessageDigest; +use openssl::bn::BigNum; +use openssl::pkey::Private; +use rust_util::information; +use ssh_key::MPInt; +use ssh_key::public::KeyData; + +#[derive(Clone, PartialEq, Debug)] +struct Identity { + pubkey: PublicKey, + privkey: PrivateKey, + comment: String, +} + +struct KeyStorage { + identities: RwLock>, +} + +impl KeyStorage { + fn new() -> Self { + let rsa = Rsa::generate(2048).unwrap(); + let mut n = rsa.n().to_vec(); + if n[0] >= 128 { + n.insert(0, 0x00); + } + let pubkey = PublicKey::Rsa(RsaPublicKey { + e: rsa.e().to_vec(), + n: n, + }); + let privkey = PrivateKey::Rsa(RsaPrivateKey { + e: rsa.e().to_vec(), + n: rsa.n().to_vec(), + d: rsa.d().to_vec(), + iqmp: rsa.iqmp().unwrap().to_vec(), + p: rsa.p().unwrap().to_vec(), + q: rsa.q().unwrap().to_vec(), + }); + let ident = Identity { + pubkey, + privkey, + comment: "testkey".to_string(), + }; + + let pubkey = ssh_key::PublicKey::from(KeyData::Rsa(ssh_key::public::RsaPublicKey { + e: MPInt::from_bytes(&rsa.e().to_vec()).unwrap(), + n: MPInt::from_bytes(&rsa.n().to_vec()).unwrap(), + })); + information!("{}", pubkey.to_string()); + Self { + identities: RwLock::new(vec![ident]) + } + } + + fn identity_index_from_pubkey( + identities: &Vec, + pubkey: &PublicKey, + ) -> Option { + for (index, identity) in identities.iter().enumerate() { + if &identity.pubkey == pubkey { + return Some(index); + } + } + return None; + } + + fn identity_from_pubkey(&self, pubkey: &PublicKey) -> Option { + let identities = self.identities.read().unwrap(); + + let index = Self::identity_index_from_pubkey(&identities, pubkey)?; + Some(identities[index].clone()) + } + + fn identity_add(&self, identity: Identity) { + let mut identities = self.identities.write().unwrap(); + if Self::identity_index_from_pubkey(&identities, &identity.pubkey) == None { + identities.push(identity); + } + } + + fn identity_remove(&self, pubkey: &PublicKey) -> Result<(), Box> { + let mut identities = self.identities.write().unwrap(); + + if let Some(index) = Self::identity_index_from_pubkey(&identities, &pubkey) { + identities.remove(index); + Ok(()) + } else { + Err(From::from("Failed to remove identity: identity not found")) + } + } + + fn sign(&self, sign_request: &SignRequest) -> Result> { + let pubkey: PublicKey = from_bytes(&sign_request.pubkey_blob)?; + + if let Some(identity) = self.identity_from_pubkey(&pubkey) { + match identity.privkey { + PrivateKey::Rsa(ref key) => { + let algorithm; + let digest; + + if sign_request.flags & signature::RSA_SHA2_512 != 0 { + algorithm = "rsa-sha2-512"; + digest = MessageDigest::sha512(); + } else if sign_request.flags & signature::RSA_SHA2_256 != 0 { + algorithm = "rsa-sha2-256"; + digest = MessageDigest::sha256(); + } else { + algorithm = "ssh-rsa"; + digest = MessageDigest::sha1(); + } + + let keypair = PKey::from_rsa(rsa_openssl_from_ssh(key)?)?; + let mut signer = Signer::new(digest, &keypair)?; + signer.update(&sign_request.data)?; + + Ok(Signature { + algorithm: algorithm.to_string(), + blob: signer.sign_to_vec()?, + }) + } + _ => Err(From::from("Signature for key type not implemented")) + } + } else { + Err(From::from("Failed to create signature: identity not found")) + } + } + + fn handle_message(&self, request: Message) -> Result> { + information!("Request: {:?}", request); + let response = match request { + Message::RequestIdentities => { + let mut identities = vec![]; + for identity in self.identities.read().unwrap().iter() { + identities.push(message::Identity { + pubkey_blob: to_bytes(&identity.pubkey)?, + comment: identity.comment.clone(), + }) + } + identities.iter().for_each(|i| { + information!("ssh-rsa {} {}", base64::encode(&i.pubkey_blob), &i.comment); + // information!(">> {}", String::from_utf8_lossy(&i.pubkey_blob)); + }); + + Ok(Message::IdentitiesAnswer(identities)) + } + Message::RemoveIdentity(identity) => { + let pubkey: PublicKey = from_bytes(&identity.pubkey_blob)?; + self.identity_remove(&pubkey)?; + Ok(Message::Success) + } + Message::AddIdentity(identity) => { + self.identity_add(Identity { + pubkey: PublicKey::from(&identity.privkey), + privkey: identity.privkey, + comment: identity.comment, + }); + Ok(Message::Success) + } + Message::SignRequest(request) => { + let signature = to_bytes(&self.sign(&request)?)?; + Ok(Message::SignResponse(signature)) + } + _ => Err(From::from(format!("Unknown message: {:?}", request))) + }; + information!("Response {:?}", response); + return response; + } +} + +impl Agent for KeyStorage { + type Error = (); + + fn handle(&self, message: Message) -> Result { + self.handle_message(message).or_else(|error| { + println!("Error handling message - {:?}", error); + Ok(Message::Failure) + }) + } +} + + +fn rsa_openssl_from_ssh(ssh_rsa: &RsaPrivateKey) -> Result, Box> { + let n = BigNum::from_slice(&ssh_rsa.n)?; + let e = BigNum::from_slice(&ssh_rsa.e)?; + let d = BigNum::from_slice(&ssh_rsa.d)?; + let qi = BigNum::from_slice(&ssh_rsa.iqmp)?; + let p = BigNum::from_slice(&ssh_rsa.p)?; + let q = BigNum::from_slice(&ssh_rsa.q)?; + let dp = &d % &(&p - &BigNum::from_u32(1)?); + let dq = &d % &(&q - &BigNum::from_u32(1)?); + + Ok(Rsa::from_private_components(n, e, d, p, q, dp, dq, qi)?) +} + +// SSH_AUTH_SOCK=connect.sock ssh root@example.com +// SSH_AUTH_SOCK=connect.sock ssh-add -l +fn main() -> Result<(), Box> { + let agent = KeyStorage::new(); + let socket = "connect.sock"; + let _ = remove_file(socket); + + information!("Start unix socket: {}", socket); + agent.run_unix(socket)?; + Ok(()) +} \ No newline at end of file