diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1db68de..3c33522 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,13 @@ jobs: formatting: name: Check formatting - runs-on: ubuntu-latest + strategy: + matrix: + include: + - os: ubuntu-latest + - os: macos-latest + - os: windows-latest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Check formatting @@ -30,7 +36,13 @@ jobs: tests: name: Unit tests - runs-on: ubuntu-latest + strategy: + matrix: + include: + - os: ubuntu-latest + - os: macos-latest + - os: windows-latest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Build and test @@ -38,7 +50,13 @@ jobs: lints: name: Clippy lints - runs-on: ubuntu-latest + strategy: + matrix: + include: + - os: ubuntu-latest + - os: macos-latest + - os: windows-latest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Check for lints diff --git a/Cargo.lock b/Cargo.lock index a3e50be..7e411e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,10 +107,19 @@ dependencies = [ ] [[package]] -name = "bitflags" -version = "2.4.2" +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] name = "byteorder" @@ -145,6 +154,53 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + [[package]] name = "env_filter" version = "0.1.0" @@ -168,21 +224,6 @@ dependencies = [ "log", ] -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "futures" version = "0.3.30" @@ -272,6 +313,27 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.1" @@ -290,12 +352,27 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "log" version = "0.4.20" @@ -328,6 +405,53 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -354,41 +478,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "openssl" -version = "0.10.63" +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-sys" -version = "0.9.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "base64ct", ] [[package]] @@ -404,10 +499,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pkg-config" -version = "0.3.29" +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" @@ -427,6 +543,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "regex" version = "1.10.3" @@ -456,6 +602,28 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "sha1", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -484,9 +652,41 @@ dependencies = [ [[package]] name = "service-binding" -version = "1.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8418a60a094aaaf2bd38ed3ee4fea0b188bf5903a9655c144085f1464c221b" +checksum = "2ef733c6f7034fe1a9c3d812cdef93f2bec735e22b0467f84063de142be42428" + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] [[package]] name = "slab" @@ -497,6 +697,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "socket2" version = "0.5.5" @@ -507,6 +713,22 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "ssh-agent-lib" version = "0.3.0" @@ -517,13 +739,21 @@ dependencies = [ "env_logger", "futures", "log", - "openssl", + "rand", + "rsa", "serde", "service-binding", + "sha1", "tokio", "tokio-util", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "2.0.48" @@ -595,6 +825,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -608,10 +844,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] -name = "vcpkg" -version = "0.2.15" +name = "version_check" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" @@ -750,3 +986,9 @@ name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index 96963d2..eeb34d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ futures = { version = "0.3.30", optional = true } log = { version = "0.4.6", optional = true } tokio = { version = "1", optional = true, features = ["rt", "net"] } tokio-util = { version = "0.7.1", optional = true, features = ["codec"] } -service-binding = "1.1.0" +service-binding = { version = "^2" } [features] default = ["agent"] @@ -36,5 +36,7 @@ required-features = ["agent"] [dev-dependencies] env_logger = "0.11.0" -openssl = "0.10.16" +rand = "0.8.5" +rsa = { version = "0.9.6", features = ["sha2", "sha1"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +sha1 = { version = "0.10.5", default-features = false, features = ["oid"] } diff --git a/README.md b/README.md index 765af91..4f60e20 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,14 @@ This makes it possible to utilize remote keys not supported by the default OpenS ## Example -This example starts listening on a Unix socket `ssh-agent.sock` and processes requests. +The following example starts listening on a socket and processing requests. +On Unix it uses `ssh-agent.sock` Unix domain socket while on Windows it uses a named pipe `\\.\pipe\agent`. ```rust,no_run +#[cfg(not(windows))] use tokio::net::UnixListener; +#[cfg(windows)] +use ssh_agent_lib::agent::NamedPipeListener; use ssh_agent_lib::agent::{Session, Agent}; use ssh_agent_lib::proto::message::Message; @@ -35,6 +39,7 @@ impl Session for MyAgent { } #[tokio::main] +#[cfg(not(windows))] async fn main() -> Result<(), Box> { let socket = "ssh-agent.sock"; let _ = std::fs::remove_file(socket); // remove the socket if exists @@ -42,6 +47,13 @@ async fn main() -> Result<(), Box> { MyAgent.listen(UnixListener::bind(socket)?).await?; Ok(()) } + +#[tokio::main] +#[cfg(windows)] +async fn main() -> Result<(), Box> { + MyAgent.listen(NamedPipeListener::new(r"\\.\pipe\agent".into())?).await?; + Ok(()) +} ``` Now, point your OpenSSH client to this socket using `SSH_AUTH_SOCK` environment variable and it will transparently use the agent: @@ -50,6 +62,12 @@ Now, point your OpenSSH client to this socket using `SSH_AUTH_SOCK` environment SSH_AUTH_SOCK=ssh-agent.sock ssh user@example.com ``` +On Windows the path of the pipe has to be used: + +```sh +SSH_AUTH_SOCK=\\.\pipe\agent ssh user@example.com +``` + For more elaborate example see the `examples` directory or [crates using `ssh-agent-lib`](https://crates.io/crates/ssh-agent-lib/reverse_dependencies). ## Note diff --git a/examples/key_storage.rs b/examples/key_storage.rs index 5abb2da..dec2940 100644 --- a/examples/key_storage.rs +++ b/examples/key_storage.rs @@ -1,24 +1,25 @@ use async_trait::async_trait; use log::info; +#[cfg(windows)] +use ssh_agent_lib::agent::NamedPipeListener; +#[cfg(not(windows))] use tokio::net::UnixListener; use ssh_agent_lib::agent::{Agent, Session}; use ssh_agent_lib::proto::message::{self, Message, SignRequest}; -use ssh_agent_lib::proto::private_key::{PrivateKey, RsaPrivateKey}; +use ssh_agent_lib::proto::private_key::PrivateKey; use ssh_agent_lib::proto::public_key::PublicKey; use ssh_agent_lib::proto::signature::{self, Signature}; use ssh_agent_lib::proto::{from_bytes, to_bytes}; use std::error::Error; -use std::fs::remove_file; -use std::sync::RwLock; +use std::sync::{Arc, Mutex}; -use openssl::bn::BigNum; -use openssl::hash::MessageDigest; -use openssl::pkey::PKey; -use openssl::pkey::Private; -use openssl::rsa::Rsa; -use openssl::sign::Signer; +use rsa::pkcs1v15::SigningKey; +use rsa::sha2::{Sha256, Sha512}; +use rsa::signature::{RandomizedSigner, SignatureEncoding}; +use rsa::BigUint; +use sha1::Sha1; #[derive(Clone, PartialEq, Debug)] struct Identity { @@ -28,43 +29,37 @@ struct Identity { } struct KeyStorage { - identities: RwLock>, + identities: Arc>>, } impl KeyStorage { - fn new() -> Self { - Self { - identities: RwLock::new(vec![]), - } - } - 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; + None } fn identity_from_pubkey(&self, pubkey: &PublicKey) -> Option { - let identities = self.identities.read().unwrap(); + let identities = self.identities.lock().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 { + let mut identities = self.identities.lock().unwrap(); + if Self::identity_index_from_pubkey(&identities, &identity.pubkey).is_none() { identities.push(identity); } } fn identity_remove(&self, pubkey: &PublicKey) -> Result<(), Box> { - let mut identities = self.identities.write().unwrap(); + let mut identities = self.identities.lock().unwrap(); - if let Some(index) = Self::identity_index_from_pubkey(&identities, &pubkey) { + if let Some(index) = Self::identity_index_from_pubkey(&identities, pubkey) { identities.remove(index); Ok(()) } else { @@ -79,26 +74,33 @@ impl KeyStorage { match identity.privkey { PrivateKey::Rsa(ref key) => { let algorithm; - let digest; - if sign_request.flags & signature::RSA_SHA2_512 != 0 { + let private_key = rsa::RsaPrivateKey::from_components( + BigUint::from_bytes_be(&key.n), + BigUint::from_bytes_be(&key.e), + BigUint::from_bytes_be(&key.d), + vec![ + BigUint::from_bytes_be(&key.p), + BigUint::from_bytes_be(&key.q), + ], + )?; + let mut rng = rand::thread_rng(); + let data = &sign_request.data; + + let signature = if sign_request.flags & signature::RSA_SHA2_512 != 0 { algorithm = "rsa-sha2-512"; - digest = MessageDigest::sha512(); + SigningKey::::new(private_key).sign_with_rng(&mut rng, data) } else if sign_request.flags & signature::RSA_SHA2_256 != 0 { algorithm = "rsa-sha2-256"; - digest = MessageDigest::sha256(); + SigningKey::::new(private_key).sign_with_rng(&mut rng, data) } 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)?; + SigningKey::::new(private_key).sign_with_rng(&mut rng, data) + }; Ok(Signature { algorithm: algorithm.to_string(), - blob: signer.sign_to_vec()?, + blob: signature.to_bytes().to_vec(), }) } _ => Err(From::from("Signature for key type not implemented")), @@ -113,7 +115,7 @@ impl KeyStorage { let response = match request { Message::RequestIdentities => { let mut identities = vec![]; - for identity in self.identities.read().unwrap().iter() { + for identity in self.identities.lock().unwrap().iter() { identities.push(message::Identity { pubkey_blob: to_bytes(&identity.pubkey)?, comment: identity.comment.clone(), @@ -141,7 +143,7 @@ impl KeyStorage { _ => Err(From::from(format!("Unknown message: {:?}", request))), }; info!("Response {:?}", response); - return response; + response } } @@ -152,33 +154,43 @@ impl Session for KeyStorage { } } -impl Agent for KeyStorage { - fn new_session(&mut self) -> impl Session { - KeyStorage::new() +struct KeyStorageAgent { + identities: Arc>>, +} + +impl KeyStorageAgent { + fn new() -> Self { + Self { + identities: Arc::new(Mutex::new(vec![])), + } } } -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)?) +impl Agent for KeyStorageAgent { + fn new_session(&mut self) -> impl Session { + KeyStorage { + identities: Arc::clone(&self.identities), + } + } } -#[tokio::main(flavor = "current_thread")] -async fn main() -> Result<(), Box> { - let agent = KeyStorage::new(); - let socket = "connect.sock"; - let _ = remove_file(socket); - env_logger::init(); - let socket = UnixListener::bind(socket)?; +#[tokio::main] +#[cfg(not(windows))] +async fn main() -> Result<(), Box> { + let socket = "ssh-agent.sock"; + let _ = std::fs::remove_file(socket); // remove the socket if exists + + KeyStorageAgent::new() + .listen(UnixListener::bind(socket)?) + .await?; + Ok(()) +} - agent.listen(socket).await?; +#[tokio::main] +#[cfg(windows)] +async fn main() -> Result<(), Box> { + KeyStorageAgent::new() + .listen(NamedPipeListener::new(r"\\.\pipe\agent".into())?) + .await?; Ok(()) } diff --git a/src/agent.rs b/src/agent.rs index 86d18ee..15ff966 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -4,7 +4,11 @@ use bytes::{Buf, BufMut, BytesMut}; use futures::{SinkExt, TryStreamExt}; use log::{error, info}; use tokio::io::{AsyncRead, AsyncWrite}; -use tokio::net::{TcpListener, TcpStream, UnixListener, UnixStream}; +#[cfg(windows)] +use tokio::net::windows::named_pipe::{NamedPipeServer, ServerOptions}; +use tokio::net::{TcpListener, TcpStream}; +#[cfg(unix)] +use tokio::net::{UnixListener, UnixStream}; use tokio_util::codec::{Decoder, Encoder, Framed}; use std::fmt; @@ -56,13 +60,14 @@ impl Encoder for MessageCodec { pub trait ListeningSocket { type Stream: fmt::Debug + AsyncRead + AsyncWrite + Send + Unpin + 'static; - async fn accept(&self) -> io::Result; + async fn accept(&mut self) -> io::Result; } +#[cfg(unix)] #[async_trait] impl ListeningSocket for UnixListener { type Stream = UnixStream; - async fn accept(&self) -> io::Result { + async fn accept(&mut self) -> io::Result { UnixListener::accept(self).await.map(|(s, _addr)| s) } } @@ -70,11 +75,40 @@ impl ListeningSocket for UnixListener { #[async_trait] impl ListeningSocket for TcpListener { type Stream = TcpStream; - async fn accept(&self) -> io::Result { + async fn accept(&mut self) -> io::Result { TcpListener::accept(self).await.map(|(s, _addr)| s) } } +#[cfg(windows)] +#[derive(Debug)] +pub struct NamedPipeListener(NamedPipeServer, std::ffi::OsString); + +#[cfg(windows)] +impl NamedPipeListener { + pub fn new(pipe: std::ffi::OsString) -> std::io::Result { + Ok(NamedPipeListener( + ServerOptions::new() + .first_pipe_instance(true) + .create(&pipe)?, + pipe, + )) + } +} + +#[cfg(windows)] +#[async_trait] +impl ListeningSocket for NamedPipeListener { + type Stream = NamedPipeServer; + async fn accept(&mut self) -> io::Result { + self.0.connect().await?; + Ok(std::mem::replace( + &mut self.0, + ServerOptions::new().create(&self.1)?, + )) + } +} + #[async_trait] pub trait Session: 'static + Sync + Send + Sized { async fn handle(&mut self, message: Message) -> Result>; @@ -109,7 +143,7 @@ pub trait Session: 'static + Sync + Send + Sized { #[async_trait] pub trait Agent: 'static + Sync + Send + Sized { fn new_session(&mut self) -> impl Session; - async fn listen(mut self, socket: S) -> Result<(), AgentError> + async fn listen(mut self, mut socket: S) -> Result<(), AgentError> where S: ListeningSocket + fmt::Debug + Send, { @@ -134,12 +168,21 @@ pub trait Agent: 'static + Sync + Send + Sized { } async fn bind(mut self, listener: service_binding::Listener) -> Result<(), AgentError> { match listener { + #[cfg(unix)] service_binding::Listener::Unix(listener) => { self.listen(UnixListener::from_std(listener)?).await } service_binding::Listener::Tcp(listener) => { self.listen(TcpListener::from_std(listener)?).await } + #[cfg(windows)] + service_binding::Listener::NamedPipe(pipe) => { + self.listen(NamedPipeListener::new(pipe)?).await + } + #[cfg(not(windows))] + service_binding::Listener::NamedPipe(_) => Err(AgentError::IO(std::io::Error::other( + "Named pipes supported on Windows only", + ))), } } }