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