diff --git a/.justfile b/.justfile index 323e1a7..86ff694 100755 --- a/.justfile +++ b/.justfile @@ -83,7 +83,7 @@ fix: # try to fix rustc issues cargo fix --allow-staged # try to fix clippy issues - cargo clippy --fix --allow-staged + cargo clippy --fix --allow-staged --allow-dirty # fmt must be last as clippy's changes may break formatting cargo +nightly fmt --all diff --git a/Cargo.lock b/Cargo.lock index f9d4970..09d7ee7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,9 +295,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" [[package]] name = "camellia" diff --git a/examples/agent-socket-info.rs b/examples/agent-socket-info.rs index f819b6c..1dc075e 100644 --- a/examples/agent-socket-info.rs +++ b/examples/agent-socket-info.rs @@ -27,7 +27,7 @@ impl Session for AgentSocketInfo { async fn request_identities(&mut self) -> Result, AgentError> { Ok(vec![Identity { // this is just a dummy key, the comment is important - pubkey: KeyData::Ed25519(ssh_key::public::Ed25519PublicKey([0; 32])), + pubkey: KeyData::Ed25519(ssh_key::public::Ed25519PublicKey([0; 32])).into(), comment: self.comment.clone(), }]) } diff --git a/examples/key-storage.rs b/examples/key-storage.rs index 934f35a..fba18bf 100644 --- a/examples/key-storage.rs +++ b/examples/key-storage.rs @@ -12,8 +12,8 @@ use ssh_agent_lib::agent::{listen, Session}; use ssh_agent_lib::error::AgentError; use ssh_agent_lib::proto::extension::{QueryResponse, RestrictDestination, SessionBind}; use ssh_agent_lib::proto::{ - message, signature, AddIdentity, AddIdentityConstrained, AddSmartcardKeyConstrained, - Credential, Extension, KeyConstraint, RemoveIdentity, SignRequest, SmartcardKey, + message, signature, AddIdentity, AddIdentityConstrained, AddSmartcardKeyConstrained, Extension, + KeyConstraint, PrivateCredential, RemoveIdentity, SignRequest, SmartcardKey, }; use ssh_key::{ private::{KeypairData, PrivateKey}, @@ -74,7 +74,7 @@ impl KeyStorage { #[crate::async_trait] impl Session for KeyStorage { async fn sign(&mut self, sign_request: SignRequest) -> Result { - let pubkey: PublicKey = sign_request.pubkey.clone().into(); + let pubkey: PublicKey = sign_request.pubkey.key_data().clone().into(); if let Some(identity) = self.identity_from_pubkey(&pubkey) { match identity.privkey.key_data() { @@ -113,7 +113,7 @@ impl Session for KeyStorage { let mut identities = vec![]; for identity in self.identities.lock().unwrap().iter() { identities.push(message::Identity { - pubkey: identity.pubkey.key_data().clone(), + pubkey: identity.pubkey.key_data().clone().into(), comment: identity.comment.clone(), }) } @@ -121,7 +121,7 @@ impl Session for KeyStorage { } async fn add_identity(&mut self, identity: AddIdentity) -> Result<(), AgentError> { - if let Credential::Key { privkey, comment } = identity.credential { + if let PrivateCredential::Key { privkey, comment } = identity.credential { let privkey = PrivateKey::try_from(privkey).map_err(AgentError::other)?; self.identity_add(Identity { pubkey: PublicKey::from(&privkey), @@ -152,7 +152,7 @@ impl Session for KeyStorage { info!("Destination constraint: {destination:?}"); } - if let Credential::Key { privkey, comment } = identity.credential.clone() { + if let PrivateCredential::Key { privkey, comment } = identity.credential.clone() { let privkey = PrivateKey::try_from(privkey).map_err(AgentError::other)?; self.identity_add(Identity { pubkey: PublicKey::from(&privkey), diff --git a/examples/openpgp-card-agent.rs b/examples/openpgp-card-agent.rs index 0f952d2..5935105 100644 --- a/examples/openpgp-card-agent.rs +++ b/examples/openpgp-card-agent.rs @@ -71,7 +71,7 @@ impl CardSession { if let AlgorithmAttributes::Ecc(ecc) = e.algo() { if ecc.ecc_type() == EccType::EdDSA { let pubkey = KeyData::Ed25519(Ed25519PublicKey(e.data().try_into()?)); - if pubkey == request.pubkey { + if pubkey == *request.pubkey.key_data() { let pin = self.pwds.get(&ident).await; return if let Some(pin) = pin { let str = pin.expose_secret().as_bytes().to_vec(); @@ -173,7 +173,8 @@ impl Session for CardSession { return Ok::<_, Box>(Some(Identity { pubkey: KeyData::Ed25519(Ed25519PublicKey( e.data().try_into()?, - )), + )) + .into(), comment: ident, })); } @@ -231,7 +232,8 @@ impl Session for CardSession { return Ok::<_, Box>(Some(Identity { pubkey: KeyData::Ed25519(Ed25519PublicKey( e.data().try_into()?, - )), + )) + .into(), comment: ident, })); } diff --git a/examples/pgp-wrapper.rs b/examples/pgp-wrapper.rs index 61af481..d52c4b0 100644 --- a/examples/pgp-wrapper.rs +++ b/examples/pgp-wrapper.rs @@ -64,7 +64,7 @@ use service_binding::Binding; use ssh_agent_lib::{ agent::Session, client::connect, - proto::{Extension, SignRequest}, + proto::{Extension, PublicCredential, SignRequest}, }; use ssh_key::public::KeyData; use tokio::runtime::Runtime; @@ -295,7 +295,7 @@ impl SecretKeyTrait for WrappedKey { .block_on(async { let mut client = self.client.lock().await; let result = client.sign(SignRequest { - pubkey: self.pubkey.clone(), + pubkey: self.pubkey.clone().into(), data: data.to_vec(), flags: 0, }); @@ -372,7 +372,8 @@ fn main() -> testresult::TestResult { let mut keyflags = KeyFlags::default(); keyflags.set_encrypt_comms(true); keyflags.set_encrypt_storage(true); - let pk = ssh_to_pgp(decryption_id.pubkey.clone(), KeyRole::Decryption); + let pubkey = decryption_id.pubkey.key_data(); + let pk = ssh_to_pgp(pubkey.clone(), KeyRole::Decryption); vec![pgp::PublicSubkey::new( pgp::packet::PublicSubkey::new( pk.packet_version(), @@ -388,6 +389,9 @@ fn main() -> testresult::TestResult { vec![] }; + let PublicCredential::Key(pubkey) = pubkey else { + panic!("Only pubkeys are supported."); + }; let signer = WrappedKey::new(pubkey.clone(), client, KeyRole::Signing); let mut keyflags = KeyFlags::default(); keyflags.set_sign(true); @@ -411,6 +415,9 @@ fn main() -> testresult::TestResult { signed_pk.to_writer(&mut std::io::stdout())?; } Args::Sign => { + let PublicCredential::Key(pubkey) = pubkey else { + panic!("Only pubkeys are supported."); + }; let signer = WrappedKey::new(pubkey.clone(), client, KeyRole::Signing); let signature = SignatureConfig::new_v4( SignatureVersion::V4, @@ -445,8 +452,8 @@ fn main() -> testresult::TestResult { pgp::packet::write_packet(&mut std::io::stdout(), &signature)?; } Args::Decrypt => { - let decryptor = - WrappedKey::new(decrypt_ids[0].pubkey.clone(), client, KeyRole::Decryption); + let pubkey = decrypt_ids[0].pubkey.key_data(); + let decryptor = WrappedKey::new(pubkey.clone(), client, KeyRole::Decryption); let message = Message::from_bytes(std::io::stdin())?; let Message::Encrypted { esk, edata } = message else { diff --git a/src/proto/message.rs b/src/proto/message.rs index c5b7a83..11c3031 100644 --- a/src/proto/message.rs +++ b/src/proto/message.rs @@ -1,6 +1,7 @@ //! Agent protocol message structures. mod add_remove; +mod credential; mod extension; mod identity; mod request; @@ -9,7 +10,8 @@ mod sign; mod unparsed; pub use self::{ - add_remove::*, extension::*, identity::*, request::*, response::*, sign::*, unparsed::*, + add_remove::*, credential::*, extension::*, identity::*, request::*, response::*, sign::*, + unparsed::*, }; #[doc(hidden)] /// For compatibility with pre-0.5.0 type alias in this module diff --git a/src/proto/message/add_remove.rs b/src/proto/message/add_remove.rs index 1b29d8d..3f843a5 100644 --- a/src/proto/message/add_remove.rs +++ b/src/proto/message/add_remove.rs @@ -1,15 +1,14 @@ //! Add a key to an agent with or without constraints and supporting data types. mod constrained; -mod credential; pub use constrained::*; -pub use credential::*; use secrecy::ExposeSecret as _; use secrecy::SecretString; use ssh_encoding::{self, CheckedSum, Decode, Encode, Reader, Writer}; use ssh_key::public::KeyData; +use super::PrivateCredential; use crate::proto::{Error, Result}; /// Add a key to an agent. @@ -20,14 +19,14 @@ use crate::proto::{Error, Result}; #[derive(Clone, PartialEq, Debug)] pub struct AddIdentity { /// A credential (private & public key, or private key / certificate) to add to the agent - pub credential: Credential, + pub credential: PrivateCredential, } impl Decode for AddIdentity { type Error = Error; fn decode(reader: &mut impl Reader) -> Result { - let credential = Credential::decode(reader)?; + let credential = PrivateCredential::decode(reader)?; Ok(Self { credential }) } diff --git a/src/proto/message/add_remove/credential.rs b/src/proto/message/credential.rs similarity index 66% rename from src/proto/message/add_remove/credential.rs rename to src/proto/message/credential.rs index 22efdc5..d85b373 100644 --- a/src/proto/message/add_remove/credential.rs +++ b/src/proto/message/credential.rs @@ -1,8 +1,9 @@ //! A container for a public / private key pair, or a certificate / private key. -use core::str::FromStr; +use std::str::FromStr as _; use ssh_encoding::{self, CheckedSum, Decode, Encode, Reader, Writer}; +use ssh_key::public::KeyData; use ssh_key::{certificate::Certificate, private::KeypairData, Algorithm}; use crate::proto::{Error, PrivateKeyData, Result}; @@ -16,7 +17,7 @@ use crate::proto::{Error, PrivateKeyData, Result}; /// This structure covers both types of identities a user may /// send to an agent as part of a [`Request::AddIdentity`](crate::proto::Request::AddIdentity) message. #[derive(Clone, PartialEq, Debug)] -pub enum Credential { +pub enum PrivateCredential { /// A public/private key pair Key { /// Public/private key pair data @@ -42,7 +43,7 @@ pub enum Credential { }, } -impl Decode for Credential { +impl Decode for PrivateCredential { type Error = Error; fn decode(reader: &mut impl Reader) -> Result { @@ -57,7 +58,7 @@ impl Decode for Credential { let privkey = PrivateKeyData::decode_as(reader, algorithm.clone())?; let comment = String::decode(reader)?; - Ok(Credential::Cert { + Ok(PrivateCredential::Cert { algorithm, certificate, privkey, @@ -67,12 +68,12 @@ impl Decode for Credential { let algorithm = Algorithm::from_str(&alg).map_err(ssh_encoding::Error::from)?; let privkey = KeypairData::decode_as(reader, algorithm)?; let comment = String::decode(reader)?; - Ok(Credential::Key { privkey, comment }) + Ok(PrivateCredential::Key { privkey, comment }) } } } -impl Encode for Credential { +impl Encode for PrivateCredential { fn encoded_len(&self) -> ssh_encoding::Result { match self { Self::Key { privkey, comment } => { @@ -113,3 +114,62 @@ impl Encode for Credential { } } } + +#[derive(Debug, PartialEq, Eq, Clone)] +/// Represents a public credential. +pub enum PublicCredential { + /// Plain public key. + Key(KeyData), + /// Signed public key. + Cert(Certificate), +} + +impl PublicCredential { + /// Returns a reference to the [KeyData]. + pub fn key_data(&self) -> &KeyData { + match self { + Self::Key(key) => key, + Self::Cert(cert) => cert.public_key(), + } + } +} + +impl Decode for PublicCredential { + type Error = Error; + + fn decode(reader: &mut impl Reader) -> core::result::Result { + // TODO: implement parsing certificates + Ok(Self::Key(KeyData::decode(reader)?)) + } +} + +impl Encode for PublicCredential { + fn encoded_len(&self) -> std::result::Result { + match self { + Self::Key(pubkey) => pubkey.encoded_len(), + Self::Cert(certificate) => certificate.encoded_len(), + } + } + + fn encode( + &self, + writer: &mut impl ssh_encoding::Writer, + ) -> std::result::Result<(), ssh_encoding::Error> { + match self { + Self::Key(pubkey) => pubkey.encode(writer), + Self::Cert(certificate) => certificate.encode(writer), + } + } +} + +impl From for PublicCredential { + fn from(value: KeyData) -> Self { + Self::Key(value) + } +} + +impl From for PublicCredential { + fn from(value: Certificate) -> Self { + Self::Cert(value) + } +} diff --git a/src/proto/message/identity.rs b/src/proto/message/identity.rs index bf56403..24080a4 100644 --- a/src/proto/message/identity.rs +++ b/src/proto/message/identity.rs @@ -1,8 +1,8 @@ //! Data returned to the client when listing keys. use ssh_encoding::{self, CheckedSum, Decode, Encode, Reader, Writer}; -use ssh_key::public::KeyData; +use super::PublicCredential; use crate::proto::{Error, Result}; /// Data returned to the client when listing keys. @@ -13,7 +13,7 @@ use crate::proto::{Error, Result}; #[derive(Clone, PartialEq, Debug)] pub struct Identity { /// A standard public-key encoding of an underlying key. - pub pubkey: KeyData, + pub pubkey: PublicCredential, /// A human-readable comment pub comment: String, @@ -36,7 +36,7 @@ impl Decode for Identity { type Error = Error; fn decode(reader: &mut impl Reader) -> Result { - let pubkey = reader.read_prefixed(KeyData::decode)?; + let pubkey = reader.read_prefixed(PublicCredential::decode)?; let comment = String::decode(reader)?; Ok(Self { pubkey, comment }) diff --git a/src/proto/message/sign.rs b/src/proto/message/sign.rs index b32f0bc..d243753 100644 --- a/src/proto/message/sign.rs +++ b/src/proto/message/sign.rs @@ -1,8 +1,8 @@ //! Signature request with data to be signed with a key in an agent. use ssh_encoding::{self, CheckedSum, Decode, Encode, Reader, Writer}; -use ssh_key::public::KeyData; +use super::PublicCredential; use crate::proto::{Error, Result}; /// Signature request with data to be signed with a key in an agent. @@ -13,7 +13,7 @@ use crate::proto::{Error, Result}; #[derive(Clone, PartialEq, Debug)] pub struct SignRequest { /// The public key portion of the [`Identity`](super::Identity) in the agent to sign the data with - pub pubkey: KeyData, + pub pubkey: PublicCredential, /// Binary data to be signed pub data: Vec, @@ -27,7 +27,7 @@ impl Decode for SignRequest { type Error = Error; fn decode(reader: &mut impl Reader) -> Result { - let pubkey = reader.read_prefixed(KeyData::decode)?; + let pubkey = reader.read_prefixed(PublicCredential::decode)?; let data = Vec::decode(reader)?; let flags = u32::decode(reader)?; diff --git a/tests/messages/req-sign-with-cert.bin.ignored b/tests/messages/req-sign-with-cert.bin.ignored new file mode 100644 index 0000000..76fff0a Binary files /dev/null and b/tests/messages/req-sign-with-cert.bin.ignored differ diff --git a/tests/messages/resp-identities-with-cert.bin.ignored b/tests/messages/resp-identities-with-cert.bin.ignored new file mode 100644 index 0000000..258e382 Binary files /dev/null and b/tests/messages/resp-identities-with-cert.bin.ignored differ diff --git a/tests/roundtrip/expected/req_add_identity_constrained_extension_restrict_destination.rs b/tests/roundtrip/expected/req_add_identity_constrained_extension_restrict_destination.rs index 83ed194..9f9c509 100644 --- a/tests/roundtrip/expected/req_add_identity_constrained_extension_restrict_destination.rs +++ b/tests/roundtrip/expected/req_add_identity_constrained_extension_restrict_destination.rs @@ -1,5 +1,8 @@ use hex_literal::hex; -use ssh_agent_lib::proto::{AddIdentity, AddIdentityConstrained, Credential, Extension, KeyConstraint, Request, Unparsed}; +use ssh_agent_lib::proto::{ + AddIdentity, AddIdentityConstrained, Extension, KeyConstraint, PrivateCredential, Request, + Unparsed, +}; use ssh_key::private::KeypairData; use super::fixtures; @@ -7,7 +10,7 @@ use super::fixtures; pub fn expected() -> Request { Request::AddIdConstrained(AddIdentityConstrained { identity: AddIdentity { - credential: Credential::Key { + credential: PrivateCredential::Key { privkey: KeypairData::Ecdsa(fixtures::demo_key()), comment: "baloo@angela".to_string(), }, diff --git a/tests/roundtrip/expected/req_add_identity_constrained_lifetime.rs b/tests/roundtrip/expected/req_add_identity_constrained_lifetime.rs index 203a388..7c6c233 100644 --- a/tests/roundtrip/expected/req_add_identity_constrained_lifetime.rs +++ b/tests/roundtrip/expected/req_add_identity_constrained_lifetime.rs @@ -1,4 +1,6 @@ -use ssh_agent_lib::proto::{AddIdentity, AddIdentityConstrained, Credential, KeyConstraint, Request}; +use ssh_agent_lib::proto::{ + AddIdentity, AddIdentityConstrained, KeyConstraint, PrivateCredential, Request, +}; use ssh_key::private::KeypairData; use super::fixtures; @@ -6,7 +8,7 @@ use super::fixtures; pub fn expected() -> Request { Request::AddIdConstrained(AddIdentityConstrained { identity: AddIdentity { - credential: Credential::Key { + credential: PrivateCredential::Key { privkey: KeypairData::Ecdsa(fixtures::demo_key()), comment: "baloo@angela".to_string(), }, diff --git a/tests/roundtrip/expected/req_add_identity_ecdsa.rs b/tests/roundtrip/expected/req_add_identity_ecdsa.rs index b79aab9..bd2d231 100644 --- a/tests/roundtrip/expected/req_add_identity_ecdsa.rs +++ b/tests/roundtrip/expected/req_add_identity_ecdsa.rs @@ -1,13 +1,13 @@ -use ssh_agent_lib::proto::{AddIdentity, Credential, Request}; +use ssh_agent_lib::proto::{AddIdentity, PrivateCredential, Request}; use ssh_key::private::KeypairData; use super::fixtures; pub fn expected() -> Request { Request::AddIdentity(AddIdentity { - credential: Credential::Key { + credential: PrivateCredential::Key { privkey: KeypairData::Ecdsa(fixtures::demo_key()), comment: "baloo@angela".to_string(), - } + }, }) } diff --git a/tests/roundtrip/expected/req_parse_certificates.rs b/tests/roundtrip/expected/req_parse_certificates.rs index b7913ad..2ec4796 100644 --- a/tests/roundtrip/expected/req_parse_certificates.rs +++ b/tests/roundtrip/expected/req_parse_certificates.rs @@ -1,15 +1,15 @@ use hex_literal::hex; -use ssh_agent_lib::proto::{AddIdentity, AddIdentityConstrained, Credential, KeyConstraint, PrivateKeyData, Request,}; -use ssh_key::{ - private::RsaPrivateKey, Algorithm, Mpint +use ssh_agent_lib::proto::{ + AddIdentity, AddIdentityConstrained, KeyConstraint, PrivateCredential, PrivateKeyData, Request, }; +use ssh_key::{private::RsaPrivateKey, Algorithm, Mpint}; use super::fixtures; pub fn expected() -> Request { Request::AddIdConstrained(AddIdentityConstrained { identity: AddIdentity { - credential: Credential::Cert { + credential: PrivateCredential::Cert { algorithm: Algorithm::new("ssh-rsa").unwrap(), certificate: fixtures::demo_certificate(), privkey: PrivateKeyData::Rsa(RsaPrivateKey { diff --git a/tests/roundtrip/expected/resp_parse_identities.rs b/tests/roundtrip/expected/resp_parse_identities.rs index 63b5d13..ec22774 100644 --- a/tests/roundtrip/expected/resp_parse_identities.rs +++ b/tests/roundtrip/expected/resp_parse_identities.rs @@ -5,7 +5,7 @@ use super::fixtures; pub fn expected() -> Response { Response::IdentitiesAnswer(vec![Identity { - pubkey: KeyData::Ecdsa(fixtures::demo_key().into()), + pubkey: KeyData::Ecdsa(fixtures::demo_key().into()).into(), comment: "baloo@angela".to_string(), }]) }