Skip to content

Commit

Permalink
Add newline to PublicKey::write_openssh_file output (#188)
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
tarcieri authored Dec 19, 2023
1 parent 151b4eb commit 56481bb
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 3 deletions.
4 changes: 3 additions & 1 deletion ssh-key/src/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down
10 changes: 8 additions & 2 deletions ssh-key/tests/private_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions ssh-key/tests/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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());
}

0 comments on commit 56481bb

Please sign in to comment.