1use crate::{Fingerprint, PrivateKey, PublicKey, Result};
4use core::fmt::{self, Debug};
5use std::{
6 env,
7 fs::{self, ReadDir},
8 path::{Path, PathBuf},
9};
10
11#[cfg(doc)]
12use crate::Error;
13
14#[derive(Clone, Eq, PartialEq)]
16pub struct DotSsh {
17 path: PathBuf,
18}
19
20impl DotSsh {
21 #[must_use]
25 pub fn new() -> Option<Self> {
26 #[allow(deprecated, reason = "TODO MSRV: Rust 1.86 un-deprecates this")]
27 env::home_dir().map(|path| Self::open(path.join(".ssh")))
28 }
29
30 pub fn open(path: impl Into<PathBuf>) -> Self {
36 let path = path.into();
37 Self {
38 path: path.canonicalize().unwrap_or(path),
39 }
40 }
41
42 #[must_use]
44 pub fn path(&self) -> &Path {
45 &self.path
46 }
47
48 #[must_use]
50 pub fn config_path(&self) -> PathBuf {
51 self.path.join("config")
52 }
53
54 pub fn private_keys(&self) -> Result<impl Iterator<Item = PrivateKey>> {
59 Ok(PrivateKeysIter {
60 read_dir: fs::read_dir(&self.path)?,
61 })
62 }
63
64 #[must_use]
66 pub fn private_key_with_fingerprint(&self, fingerprint: Fingerprint) -> Option<PrivateKey> {
67 self.private_keys()
68 .ok()?
69 .find(|key| key.public_key().fingerprint(fingerprint.algorithm()) == fingerprint)
70 }
71
72 pub fn public_keys(&self) -> Result<impl Iterator<Item = PublicKey>> {
77 Ok(PublicKeysIter {
78 read_dir: fs::read_dir(&self.path)?,
79 })
80 }
81
82 #[must_use]
84 pub fn public_key_with_fingerprint(&self, fingerprint: Fingerprint) -> Option<PublicKey> {
85 self.public_keys()
86 .ok()?
87 .find(|key| key.fingerprint(fingerprint.algorithm()) == fingerprint)
88 }
89
90 pub fn write_private_key(&self, filename: impl AsRef<Path>, key: &PrivateKey) -> Result<()> {
95 key.write_openssh_file(self.path.join(filename), Default::default())
96 }
97
98 pub fn write_public_key(&self, filename: impl AsRef<Path>, key: &PublicKey) -> Result<()> {
103 key.write_openssh_file(self.path.join(filename))
104 }
105}
106
107impl Debug for DotSsh {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 f.debug_struct("DotSsh").finish_non_exhaustive()
110 }
111}
112
113impl Default for DotSsh {
114 fn default() -> Self {
116 Self::new().expect("home directory could not be located")
117 }
118}
119
120pub(crate) struct PrivateKeysIter {
122 read_dir: ReadDir,
123}
124
125impl Iterator for PrivateKeysIter {
126 type Item = PrivateKey;
127
128 fn next(&mut self) -> Option<Self::Item> {
129 loop {
130 let entry = self.read_dir.next()?.ok()?;
131
132 if let Ok(key) = PrivateKey::read_openssh_file(entry.path()) {
133 return Some(key);
134 }
135 }
136 }
137}
138
139pub(crate) struct PublicKeysIter {
141 read_dir: ReadDir,
142}
143
144impl Iterator for PublicKeysIter {
145 type Item = PublicKey;
146
147 fn next(&mut self) -> Option<Self::Item> {
148 loop {
149 let entry = self.read_dir.next()?.ok()?;
150
151 if let Ok(key) = PublicKey::read_openssh_file(entry.path()) {
152 return Some(key);
153 }
154 }
155 }
156}