diff --git a/ssh-key/src/algorithm.rs b/ssh-key/src/algorithm.rs index ea58ff570..90a894069 100644 --- a/ssh-key/src/algorithm.rs +++ b/ssh-key/src/algorithm.rs @@ -235,6 +235,16 @@ impl Decode for EcdsaCurve { } } +impl Encode for EcdsaCurve { + fn encoded_len(&self) -> Result { + Ok(4 + self.as_str().len()) + } + + fn encode(&self, encoder: &mut base64::Encoder<'_>) -> Result<()> { + encoder.encode_str(self.as_str()) + } +} + impl fmt::Display for EcdsaCurve { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) diff --git a/ssh-key/src/public.rs b/ssh-key/src/public.rs index 714abea30..19be2cd04 100644 --- a/ssh-key/src/public.rs +++ b/ssh-key/src/public.rs @@ -86,10 +86,10 @@ impl PublicKey { /// [`String`] for the result. #[cfg(feature = "alloc")] pub fn to_openssh(&self) -> Result { - let encoded_len = 2 - + self.algorithm().as_str().len() - + (self.key_data.encoded_len()? * 4 / 3) - + self.comment.len(); + let alg_len = self.algorithm().as_str().len(); + let key_data_len = (((self.key_data.encoded_len()? * 4) / 3) + 3) & !3; + let comment_len = self.comment.len(); + let encoded_len = 2 + alg_len + key_data_len + comment_len; let mut buf = vec![0u8; encoded_len]; let actual_len = self.encode_openssh(&mut buf)?.len(); @@ -244,16 +244,21 @@ impl Encode for KeyData { fn encoded_len(&self) -> Result { let alg_len = self.algorithm().encoded_len()?; let key_len = match self { + #[cfg(feature = "ecdsa")] + Self::Ecdsa(key) => key.encoded_len()?, Self::Ed25519(key) => key.encoded_len()?, #[allow(unreachable_patterns)] _ => return Err(Error::Algorithm), }; + Ok(alg_len + key_len) } fn encode(&self, encoder: &mut base64::Encoder<'_>) -> Result<()> { self.algorithm().encode(encoder)?; match self { + #[cfg(feature = "ecdsa")] + Self::Ecdsa(key) => key.encode(encoder), Self::Ed25519(key) => key.encode(encoder), #[allow(unreachable_patterns)] _ => Err(Error::Algorithm), diff --git a/ssh-key/src/public/ecdsa.rs b/ssh-key/src/public/ecdsa.rs index 6b2c7cb1a..077168d5e 100644 --- a/ssh-key/src/public/ecdsa.rs +++ b/ssh-key/src/public/ecdsa.rs @@ -1,7 +1,7 @@ //! Elliptic Curve Digital Signature Algorithm (ECDSA) public keys. use crate::{ - base64::{self, Decode}, + base64::{self, Decode, Encode}, Algorithm, EcdsaCurve, Error, Result, }; use core::fmt; @@ -104,6 +104,17 @@ impl Decode for EcdsaPublicKey { } } +impl Encode for EcdsaPublicKey { + fn encoded_len(&self) -> Result { + Ok(4 + self.curve().encoded_len()? + self.as_ref().len()) + } + + fn encode(&self, encoder: &mut base64::Encoder<'_>) -> Result<()> { + self.curve().encode(encoder)?; + encoder.encode_byte_slice(self.as_ref()) + } +} + impl fmt::Display for EcdsaPublicKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:X}", self) diff --git a/ssh-key/tests/public_key.rs b/ssh-key/tests/public_key.rs index 19ce5c32d..00015c6e6 100644 --- a/ssh-key/tests/public_key.rs +++ b/ssh-key/tests/public_key.rs @@ -213,6 +213,27 @@ fn decode_rsa_4096_openssh() { assert_eq!("user@example.com", ossh_key.comment); } +#[cfg(all(feature = "alloc", feature = "ecdsa"))] +#[test] +fn encode_ecdsa_p256_openssh() { + let ossh_key = PublicKey::from_openssh(OSSH_ECDSA_P256_EXAMPLE).unwrap(); + assert_eq!(OSSH_ECDSA_P256_EXAMPLE.trim_end(), &ossh_key.to_string()) +} + +#[cfg(all(feature = "alloc", feature = "ecdsa"))] +#[test] +fn encode_ecdsa_p384_openssh() { + let ossh_key = PublicKey::from_openssh(OSSH_ECDSA_P384_EXAMPLE).unwrap(); + assert_eq!(OSSH_ECDSA_P384_EXAMPLE.trim_end(), &ossh_key.to_string()) +} + +#[cfg(all(feature = "alloc", feature = "ecdsa"))] +#[test] +fn encode_ecdsa_p521_openssh() { + let ossh_key = PublicKey::from_openssh(OSSH_ECDSA_P521_EXAMPLE).unwrap(); + assert_eq!(OSSH_ECDSA_P521_EXAMPLE.trim_end(), &ossh_key.to_string()) +} + #[cfg(feature = "alloc")] #[test] fn encode_ed25519_openssh() {