ssh_key/
dot_ssh.rs

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