1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! `~/.ssh` support.
use crate::{Fingerprint, PrivateKey, PublicKey, Result};
use core::fmt::{self, Debug};
use std::{
env,
fs::{self, ReadDir},
path::{Path, PathBuf},
};
#[cfg(doc)]
use crate::Error;
/// `~/.ssh` directory support (or similarly structured directories).
#[derive(Clone, Eq, PartialEq)]
pub struct DotSsh {
path: PathBuf,
}
impl DotSsh {
/// Open `~/.ssh` if the home directory can be located.
///
/// Returns `None` if the home directory couldn't be located.
#[must_use]
pub fn new() -> Option<Self> {
#[allow(deprecated, reason = "TODO MSRV: Rust 1.86 un-deprecates this")]
env::home_dir().map(|path| Self::open(path.join(".ssh")))
}
/// Open a `~/.ssh`-structured directory.
///
/// Does not verify that the directory exists or has the right file permissions.
///
/// Attempts to canonicalize the path once opened.
pub fn open(path: impl Into<PathBuf>) -> Self {
let path = path.into();
Self {
path: path.canonicalize().unwrap_or(path),
}
}
/// Get the path to the `~/.ssh` directory (or whatever [`DotSsh::open`] was called with).
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
/// Get the path to the `~/.ssh/config` configuration file. Does not check if it exists.
#[must_use]
pub fn config_path(&self) -> PathBuf {
self.path.join("config")
}
/// Iterate over the private keys in the `~/.ssh` directory.
///
/// # Errors
/// Returns [`Error::Io`] in the event of I/O errors.
pub fn private_keys(&self) -> Result<impl Iterator<Item = PrivateKey>> {
Ok(PrivateKeysIter {
read_dir: fs::read_dir(&self.path)?,
})
}
/// Find a private key whose public key has the given key fingerprint.
#[must_use]
pub fn private_key_with_fingerprint(&self, fingerprint: Fingerprint) -> Option<PrivateKey> {
self.private_keys()
.ok()?
.find(|key| key.public_key().fingerprint(fingerprint.algorithm()) == fingerprint)
}
/// Iterate over the public keys in the `~/.ssh` directory.
///
/// # Errors
/// Returns [`Error::Io`] in the event of I/O errors.
pub fn public_keys(&self) -> Result<impl Iterator<Item = PublicKey>> {
Ok(PublicKeysIter {
read_dir: fs::read_dir(&self.path)?,
})
}
/// Find a public key with the given key fingerprint.
#[must_use]
pub fn public_key_with_fingerprint(&self, fingerprint: Fingerprint) -> Option<PublicKey> {
self.public_keys()
.ok()?
.find(|key| key.fingerprint(fingerprint.algorithm()) == fingerprint)
}
/// Write a private key into `~/.ssh`.
///
/// # Errors
/// Returns [`Error::Io`] in the event of I/O errors.
pub fn write_private_key(&self, filename: impl AsRef<Path>, key: &PrivateKey) -> Result<()> {
key.write_openssh_file(self.path.join(filename), Default::default())
}
/// Write a public key into `~/.ssh`.
///
/// # Errors
/// Returns [`Error::Io`] in the event of I/O errors.
pub fn write_public_key(&self, filename: impl AsRef<Path>, key: &PublicKey) -> Result<()> {
key.write_openssh_file(self.path.join(filename))
}
}
impl Debug for DotSsh {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DotSsh").finish_non_exhaustive()
}
}
impl Default for DotSsh {
/// Calls [`DotSsh::new`] and panics if the home directory could not be located.
fn default() -> Self {
Self::new().expect("home directory could not be located")
}
}
/// Iterator over the private keys in the `~/.ssh` directory.
pub(crate) struct PrivateKeysIter {
read_dir: ReadDir,
}
impl Iterator for PrivateKeysIter {
type Item = PrivateKey;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry = self.read_dir.next()?.ok()?;
if let Ok(key) = PrivateKey::read_openssh_file(entry.path()) {
return Some(key);
}
}
}
}
/// Iterator over the public keys in the `~/.ssh` directory.
pub(crate) struct PublicKeysIter {
read_dir: ReadDir,
}
impl Iterator for PublicKeysIter {
type Item = PublicKey;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry = self.read_dir.next()?.ok()?;
if let Ok(key) = PublicKey::read_openssh_file(entry.path()) {
return Some(key);
}
}
}
}