diff --git a/Cargo.lock b/Cargo.lock index f9d4970..1624a40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -171,7 +171,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -201,6 +201,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -213,6 +219,29 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.66", + "which", +] + [[package]] name = "bitfield" version = "0.14.0" @@ -225,6 +254,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "blake2" version = "0.10.6" @@ -350,6 +385,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfb-mode" version = "0.8.2" @@ -389,6 +433,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.7" @@ -420,7 +475,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -544,7 +599,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -568,7 +623,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.66", ] [[package]] @@ -579,7 +634,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -620,7 +675,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -630,7 +685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.66", ] [[package]] @@ -727,6 +782,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -748,6 +809,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "env_filter" version = "0.1.0" @@ -777,6 +858,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -871,7 +962,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -1019,6 +1110,21 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "hostname-validator" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" + [[package]] name = "humantime" version = "2.1.0" @@ -1166,6 +1272,12 @@ dependencies = [ "spin", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.155" @@ -1183,18 +1295,40 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.48.5", +] + [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "malloced" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dfebb2f9e0b39509c62eead6ec7ae0c0ed45bb61d12bbcf4e976c566c5400ec" + [[package]] name = "md-5" version = "0.10.6" @@ -1265,6 +1399,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1323,7 +1468,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -1347,6 +1492,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "oid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2" +dependencies = [ + "serde", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -1424,13 +1578,19 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pcsc" version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45ed9d7f816b7d9ce9ddb0062dd2f393b3af31411a95a35411809b4b9116ea08" dependencies = [ - "bitflags", + "bitflags 1.3.2", "pcsc-sys", ] @@ -1443,6 +1603,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1461,7 +1627,7 @@ dependencies = [ "aes", "aes-gcm", "argon2", - "base64", + "base64 0.22.1", "bitfield", "block-padding", "blowfish", @@ -1516,6 +1682,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "picky-asn1" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295eea0f33c16be21e2a98b908fdd4d73c04dd48c8480991b76dbcf0cb58b212" +dependencies = [ + "oid", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-der" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df7873a9e36d42dadb393bea5e211fe83d793c172afad5fb4ec846ec582793f" +dependencies = [ + "picky-asn1", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-x509" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c5f20f71a68499ff32310f418a6fad8816eac1a2859ed3f0c5c741389dd6208" +dependencies = [ + "base64 0.21.7", + "oid", + "picky-asn1", + "picky-asn1-der", + "serde", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1573,6 +1774,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.66", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -1768,7 +1979,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn", + "syn 2.0.66", "unicode-ident", ] @@ -1778,6 +1989,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1787,6 +2004,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "sec1" version = "0.7.3" @@ -1825,6 +2055,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.203" @@ -1833,7 +2072,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -1887,6 +2126,12 @@ dependencies = [ "keccak", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signature" version = "2.2.0" @@ -1963,6 +2208,7 @@ dependencies = [ "secrecy", "service-binding", "sha1", + "sha2", "signature", "ssh-encoding", "ssh-key", @@ -1971,6 +2217,7 @@ dependencies = [ "thiserror", "tokio", "tokio-util", + "tss-esapi", ] [[package]] @@ -2037,6 +2284,17 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.66" @@ -2048,6 +2306,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "testresult" version = "0.4.0" @@ -2071,7 +2335,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -2098,7 +2362,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -2131,6 +2395,42 @@ dependencies = [ "winnow", ] +[[package]] +name = "tss-esapi" +version = "8.0.0-alpha" +source = "git+https://github.com/parallaxsecond/rust-tss-esapi.git#75e98791c52c3f8c4f0a3445604bdf1c76898a86" +dependencies = [ + "bitfield", + "cfg-if", + "enumflags2", + "getrandom", + "hostname-validator", + "log", + "malloced", + "num-derive", + "num-traits", + "oid", + "paste", + "picky-asn1", + "picky-asn1-x509", + "regex", + "semver", + "tss-esapi-sys", + "zeroize", +] + +[[package]] +name = "tss-esapi-sys" +version = "0.5.0" +source = "git+https://github.com/parallaxsecond/rust-tss-esapi.git#75e98791c52c3f8c4f0a3445604bdf1c76898a86" +dependencies = [ + "bindgen", + "cfg-if", + "pkg-config", + "semver", + "target-lexicon", +] + [[package]] name = "twofish" version = "0.7.1" @@ -2207,7 +2507,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -2229,7 +2529,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2240,6 +2540,18 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "widestring" version = "1.1.0" @@ -2454,5 +2766,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] diff --git a/Cargo.toml b/Cargo.toml index 0bc9150..7fce80f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ rand = "0.8.5" rsa = { version = "0.9.6", features = ["sha2", "sha1"] } tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync"] } sha1 = { version = "0.10.6", default-features = false, features = ["oid"] } +sha2 = { version = "0.10.6", default-features = false } testresult = "0.4.0" hex-literal = "0.4.1" ssh-key = { version = "0.6.6", features = ["p256", "rsa"] } @@ -58,3 +59,4 @@ retainer = "0.3.0" pgp = "0.13.0" chrono = "0.4.38" interprocess = "2.2.0" +tss-esapi = { git = "https://github.com/parallaxsecond/rust-tss-esapi.git", features = ["generate-bindings"] } diff --git a/examples/tpm-backed.rs b/examples/tpm-backed.rs new file mode 100644 index 0000000..f624573 --- /dev/null +++ b/examples/tpm-backed.rs @@ -0,0 +1,147 @@ +#![cfg(not(windows))] +//! TPM-backed storage for SSH keys + +use std::sync::{Arc, Mutex}; + +use async_trait::async_trait; +use p256::elliptic_curve::sec1::FromEncodedPoint; +use sha2::{Digest as _, Sha256}; +use ssh_key::{ + public::{EcdsaPublicKey, KeyData}, + Algorithm, EcdsaCurve, Signature, +}; +use tokio::net::UnixListener as Listener; +use tss_esapi::{ + abstraction::transient::{KeyMaterial, KeyParams, TransientKeyContextBuilder}, + interface_types::{ + algorithm::{EccSchemeAlgorithm, HashingAlgorithm}, + ecc::EccCurve, + }, + structures::{Auth, Digest, EccScheme, Signature as TSignature}, + utils::PublicKey, + Context, TctiNameConf, TransientKeyContext, +}; + +use ssh_agent_lib::{ + agent::{listen, Session}, + error::AgentError, + proto::{message, Extension, SignRequest}, +}; + +#[derive(Debug)] +struct Inner { + ctx: TransientKeyContext, + tpm_km: KeyMaterial, + tpm_auth: Option, + public: EcdsaPublicKey, +} + +#[derive(Clone, Debug)] +struct TpmBackend { + inner: Arc>, +} + +impl TpmBackend { + fn params() -> KeyParams { + KeyParams::Ecc { + curve: EccCurve::NistP256, + scheme: EccScheme::create( + EccSchemeAlgorithm::EcDsa, + Some(HashingAlgorithm::Sha256), + None, + ) + .expect("Failed to create ecc scheme"), + } + } +} + +impl Default for TpmBackend { + fn default() -> Self { + let conf = TctiNameConf::from_environment_variable() + .expect("Failed to get TCTI / TPM2TOOLS_TCTI from environment. Try `export TCTI=device:/dev/tpmrm0`"); + let mut ctx = TransientKeyContextBuilder::new() + .with_tcti(conf) + .build() + .unwrap(); + + let (tpm_km, tpm_auth) = ctx.create_key(Self::params(), 16).unwrap(); + + let PublicKey::Ecc { x, y } = tpm_km.public() else { + panic!("we asked for an ecc") + }; + let public = p256::EncodedPoint::from_affine_coordinates( + x.as_slice().into(), + y.as_slice().into(), + false, + ); + let public = EcdsaPublicKey::NistP256(public); + + let inner = Inner { + ctx, + public, + tpm_km, + tpm_auth, + }; + + Self { + inner: Arc::new(Mutex::new(inner)), + } + } +} + +#[crate::async_trait] +impl Session for TpmBackend { + async fn sign(&mut self, sign_request: SignRequest) -> Result { + let pubkey = sign_request.pubkey.clone(); + let data = &sign_request.data; + let mut inner = self.inner.lock().unwrap(); + + if KeyData::from(inner.public.clone()) == pubkey { + let hash = Sha256::digest(data).to_vec(); + let km = inner.tpm_km.clone(); + let auth = inner.tpm_auth.clone(); + let signature = inner + .ctx + .sign(km, Self::params(), auth, Digest::try_from(hash).unwrap()) + .unwrap(); + + let TSignature::EcDsa(signature) = signature else { + panic!("we asked for an ecdsa signature??"); + }; + + let signature = p256::ecdsa::Signature::from_scalars( + *p256::FieldBytes::from_slice(signature.signature_r().as_slice()), + *p256::FieldBytes::from_slice(signature.signature_s().as_slice()), + ) + .unwrap(); + + Ok(Signature::try_from(signature).unwrap()) + } else { + Err(std::io::Error::other("Failed to create signature: identity not found").into()) + } + } + + async fn request_identities(&mut self) -> Result, AgentError> { + let inner = self.inner.lock().unwrap(); + + Ok(vec![message::Identity { + pubkey: inner.public.clone().into(), + comment: "tpm backed key".to_string(), + }]) + } + + async fn extension(&mut self, extension: Extension) -> Result, AgentError> { + Ok(None) + } +} + +#[tokio::main] +async fn main() -> Result<(), AgentError> { + env_logger::init(); + + let socket = "ssh-agent.sock"; + let _ = std::fs::remove_file(socket); // remove the socket if exists + + listen(Listener::bind(socket)?, TpmBackend::default()).await?; + Ok(()) +}