From 56481bbe431d001585f2fea2f71a20dd27db703f Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Tue, 19 Dec 2023 13:32:50 -0700 Subject: [PATCH] Add newline to `PublicKey::write_openssh_file` output (#188) Also adds an integration test to ensure that the file output matches what `ssh-keygen` generates byte-for-byte. The `ToString` impl still omits the newline. To avoid making breaking changes, it always uses `\n` as the newline, however perhaps in a future breaking release it could be changed to accept `LineEnding` so as to match `PrivateKey::write_openssh_file`. --- ssh-key/src/public.rs | 4 +++- ssh-key/tests/private_key.rs | 10 ++++++++-- ssh-key/tests/public_key.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/ssh-key/src/public.rs b/ssh-key/src/public.rs index 7d8be56..8bec082 100644 --- a/ssh-key/src/public.rs +++ b/ssh-key/src/public.rs @@ -232,7 +232,9 @@ impl PublicKey { /// Write public key as an OpenSSH-formatted file. #[cfg(feature = "std")] pub fn write_openssh_file(&self, path: &Path) -> Result<()> { - let encoded = self.to_openssh()?; + let mut encoded = self.to_openssh()?; + encoded.push('\n'); // TODO(tarcieri): OS-specific line endings? + fs::write(path, encoded.as_bytes())?; Ok(()) } diff --git a/ssh-key/tests/private_key.rs b/ssh-key/tests/private_key.rs index d1d78b7..9e43137 100644 --- a/ssh-key/tests/private_key.rs +++ b/ssh-key/tests/private_key.rs @@ -12,7 +12,7 @@ use ssh_key::LineEnding; #[cfg(all(feature = "std"))] use { ssh_key::PublicKey, - std::{io, process}, + std::{io, path::PathBuf, process}, }; /// DSA OpenSSH-formatted public key @@ -46,6 +46,12 @@ const OPENSSH_RSA_4096_EXAMPLE: &str = include_str!("examples/id_rsa_4096"); #[cfg(feature = "alloc")] const OPENSSH_OPAQUE_EXAMPLE: &str = include_str!("examples/id_opaque"); +/// Get a path into the `tests/scratch` directory. +#[cfg(feature = "std")] +pub fn scratch_path(filename: &str) -> PathBuf { + PathBuf::from(&format!("tests/scratch/{}", filename)) +} + #[cfg(feature = "alloc")] #[test] fn decode_dsa_openssh() { @@ -468,7 +474,7 @@ fn encoding_integration_test(private_key: PrivateKey) { .replace(':', "-") .replace('/', "_"); - let path = std::path::PathBuf::from(&format!("tests/scratch/{}", fingerprint)); + let path = scratch_path(&fingerprint); private_key .write_openssh_file(&path, LineEnding::LF) diff --git a/ssh-key/tests/public_key.rs b/ssh-key/tests/public_key.rs index e536b82..ff0d528 100644 --- a/ssh-key/tests/public_key.rs +++ b/ssh-key/tests/public_key.rs @@ -7,6 +7,9 @@ use std::collections::HashSet; #[cfg(feature = "ecdsa")] use ssh_key::EcdsaCurve; +#[cfg(feature = "std")] +use std::{fs, path::PathBuf}; + /// DSA OpenSSH-formatted public key #[cfg(feature = "alloc")] const OPENSSH_DSA_EXAMPLE: &str = include_str!("examples/id_dsa_1024.pub"); @@ -45,6 +48,12 @@ const OPENSSH_SK_ED25519_EXAMPLE: &str = include_str!("examples/id_sk_ed25519.pu #[cfg(feature = "alloc")] const OPENSSH_OPAQUE_EXAMPLE: &str = include_str!("examples/id_opaque.pub"); +/// Get a path into the `tests/scratch` directory. +#[cfg(feature = "std")] +pub fn scratch_path(filename: &str) -> PathBuf { + PathBuf::from(&format!("tests/scratch/{}.pub", filename)) +} + #[cfg(feature = "alloc")] #[test] fn decode_dsa_openssh() { @@ -386,3 +395,20 @@ fn public_keys_are_hashable() { let set = HashSet::from([&key]); assert_eq!(true, set.contains(&key)); } + +#[cfg(feature = "std")] +#[test] +fn write_openssh_file() { + let example_key = OPENSSH_ED25519_EXAMPLE; + let key = PublicKey::from_openssh(example_key).unwrap(); + + let fingerprint = key + .fingerprint(Default::default()) + .to_string() + .replace(':', "-") + .replace('/', "_"); + + let path = scratch_path(&fingerprint); + key.write_openssh_file(&path).unwrap(); + assert_eq!(example_key, fs::read_to_string(&path).unwrap()); +}