Fetch User Keys - simple tool for fetching SSH keys from various sources
1use ssh_key::PublicKey;
2use tokio::process::Command;
3use serde::Deserialize;
4use async_trait::async_trait;
5
6#[async_trait]
7pub trait Fetch: std::fmt::Debug {
8 async fn fetch(&self) -> Vec<PublicKey>;
9}
10
11#[derive(Debug, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum Source {
14 Raw(Raw),
15 Hosts(Hosts),
16 Forge(Forge),
17 Github(String),
18 Sourcehut(String),
19 Gitlab(String),
20 Codeberg(String),
21}
22
23#[async_trait]
24impl Fetch for Source {
25 async fn fetch(&self) -> Vec<PublicKey> {
26 match *self {
27 Source::Raw(ref raw) => raw.fetch().await,
28 Source::Hosts(ref raw) => raw.fetch().await,
29 Source::Forge(ref raw) => raw.fetch().await,
30 Source::Github(ref user) => Forge { user: user.clone(), host: "github.com".into() }.fetch().await,
31 Source::Sourcehut(ref user) => Forge { user: user.clone(), host: "meta.sr.ht".into() }.fetch().await,
32 Source::Gitlab(ref user) => Forge { user: user.clone(), host: "gitlab.com".into() }.fetch().await,
33 Source::Codeberg(ref user) => Forge { user: user.clone(), host: "codeberg.org".into() }.fetch().await,
34 }
35 }
36}
37
38#[derive(Debug, Deserialize)]
39pub struct Raw(Vec<PublicKey>);
40
41#[async_trait::async_trait]
42impl Fetch for Raw {
43 async fn fetch(&self) -> Vec<PublicKey> {
44 self.0.clone()
45 }
46}
47
48#[derive(Debug, Deserialize)]
49pub struct Hosts(pub Vec<String>);
50
51#[async_trait]
52impl Fetch for Hosts {
53 async fn fetch(&self) -> Vec<PublicKey> {
54 // TODO: Check if we can do it in-process instead of shelling out to `ssh-keyscan`
55 let result = Command::new("ssh-keyscan")
56 .args(&self.0)
57 .output()
58 .await
59 .unwrap();
60
61 std::str::from_utf8(&result.stdout)
62 .unwrap()
63 .trim()
64 .split('\n')
65 .map(|line| {
66 // Ignore first column as it contain hostname which is not
67 // needed there
68 line.split(' ')
69 .skip(1)
70 .collect::<Vec<_>>()
71 .join(" ")
72 .to_owned()
73 })
74 .map(|k| PublicKey::from_openssh(&k).unwrap())
75 .collect()
76 }
77}
78
79#[derive(Debug, Deserialize)]
80pub struct Forge {
81 pub user: String,
82 pub host: String,
83}
84
85#[async_trait]
86impl Fetch for Forge {
87 async fn fetch(&self) -> Vec<PublicKey> {
88 let url = format!("https://{}/{}.keys", self.host, self.user);
89
90 reqwest::get(url)
91 .await
92 .unwrap()
93 .text()
94 .await
95 .unwrap()
96 .trim()
97 .split('\n')
98 .map(|s| PublicKey::from_openssh(s).unwrap())
99 .collect()
100 }
101}