From 81b0582fbd9788eaf1f7affab5d7cc4053901f6c Mon Sep 17 00:00:00 2001 From: Ross Williams Date: Wed, 14 Jan 2026 19:42:46 -0500 Subject: [PATCH 1/3] ssh-key: Fix KeyData Certificate encoding KeyData was duplicating the algorithm string when encoding a certificate public key to its binary representation. It was also filling in the non-certificate variant of the Algorithm string. This fixes both. Signed-off-by: Ross Williams --- ssh-key/src/public/key_data.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/ssh-key/src/public/key_data.rs b/ssh-key/src/public/key_data.rs index 1aced2d..8f9695d 100644 --- a/ssh-key/src/public/key_data.rs +++ b/ssh-key/src/public/key_data.rs @@ -301,16 +301,26 @@ impl Decode for KeyData { impl Encode for KeyData { fn encoded_len(&self) -> encoding::Result { - [ - self.algorithm().encoded_len()?, - self.encoded_key_data_len()?, - ] - .checked_sum() + if let Self::Certificate(_) = self { + // Certificate encodes its own Algorithm + self.encoded_key_data_len() + } else { + [ + self.algorithm().encoded_len()?, + self.encoded_key_data_len()?, + ] + .checked_sum() + } } fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> { - self.algorithm().encode(writer)?; - self.encode_key_data(writer) + if let Self::Certificate(_) = self { + // Certificate encodes its own Algorithm + self.encode_key_data(writer) + } else { + self.algorithm().encode(writer)?; + self.encode_key_data(writer) + } } } From 65aaccb4dff395acd3c588fe507073178317d6aa Mon Sep 17 00:00:00 2001 From: Ross Williams Date: Wed, 14 Jan 2026 20:45:45 -0500 Subject: [PATCH 2/3] ssh-key: add tests for KeyData's Encode implementation Signed-off-by: Ross Williams --- ssh-key/tests/certificate.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/ssh-key/tests/certificate.rs b/ssh-key/tests/certificate.rs index 9ce3dfe..5ba9c86 100644 --- a/ssh-key/tests/certificate.rs +++ b/ssh-key/tests/certificate.rs @@ -2,7 +2,7 @@ #![cfg(feature = "alloc")] -use encoding::Decode; +use encoding::{Base64Reader, Decode, Encode, Reader}; use hex_literal::hex; use ssh_key::{Algorithm, Certificate, public::KeyData}; use std::str::FromStr; @@ -301,6 +301,40 @@ fn decode_rsa_4096_keydata() { decode_keydata(RSA_4096_CERT_EXAMPLE) } +fn encode_keydata(certificate_str: &str) { + // Decode the certificate's binary representation directly from the base64 + let cert_base64 = certificate_str.split_whitespace().nth(1).unwrap(); + let mut base64_reader = Base64Reader::new(cert_base64.as_bytes()).unwrap(); + let mut cert_bytes = vec![0; base64_reader.remaining_len()]; + base64_reader.read(&mut cert_bytes).unwrap(); + + // Parse the certificate from the same OpenSSH public key string, then re-encode it as binary + // using KeyData's Encode implementation + let cert = Certificate::from_str(certificate_str).unwrap(); + let key_data = KeyData::from(cert); + let key_encoded = key_data.encode_vec().unwrap(); + + // Compare the decoded base64 to the parsed and re-encoded KeyData + assert_eq!(cert_bytes, key_encoded); +} + +#[cfg(feature = "ecdsa")] +#[test] +fn encode_ecdsa_keydata() { + encode_keydata(ECDSA_P256_CERT_EXAMPLE); +} + +#[cfg(feature = "ed25519")] +#[test] +fn encode_ed25519_keydata() { + encode_keydata(ED25519_CERT_EXAMPLE); +} + +#[test] +fn encode_rsa_4096_keydata() { + encode_keydata(RSA_4096_CERT_EXAMPLE); +} + #[cfg(feature = "ed25519")] #[test] fn verify_ed25519_certificate_signature() { From ad860b1d2623927e9b1407c0542ea08138edf37e Mon Sep 17 00:00:00 2001 From: Ross Williams Date: Wed, 14 Jan 2026 21:01:19 -0500 Subject: [PATCH 3/3] ssh-key: Fix KeyData encoding for non-alloc Signed-off-by: Ross Williams --- ssh-key/src/public/key_data.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/ssh-key/src/public/key_data.rs b/ssh-key/src/public/key_data.rs index 8f9695d..f0c3b01 100644 --- a/ssh-key/src/public/key_data.rs +++ b/ssh-key/src/public/key_data.rs @@ -301,26 +301,28 @@ impl Decode for KeyData { impl Encode for KeyData { fn encoded_len(&self) -> encoding::Result { - if let Self::Certificate(_) = self { + #[cfg(feature = "alloc")] + if self.is_certificate() { // Certificate encodes its own Algorithm - self.encoded_key_data_len() - } else { - [ - self.algorithm().encoded_len()?, - self.encoded_key_data_len()?, - ] - .checked_sum() + return self.encoded_key_data_len(); } + + [ + self.algorithm().encoded_len()?, + self.encoded_key_data_len()?, + ] + .checked_sum() } fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> { - if let Self::Certificate(_) = self { + #[cfg(feature = "alloc")] + if self.is_certificate() { // Certificate encodes its own Algorithm - self.encode_key_data(writer) - } else { - self.algorithm().encode(writer)?; - self.encode_key_data(writer) + return self.encode_key_data(writer); } + + self.algorithm().encode(writer)?; + self.encode_key_data(writer) } }