// SPDX-FileCopyrightText: 2024 Ɓukasz Niemier <#@hauleth.dev> // // SPDX-License-Identifier: EUPL-1.2 use ssh_key::PublicKey; use tokio::process::Command; use serde::Deserialize; use async_trait::async_trait; #[async_trait] pub trait Fetch: std::fmt::Debug { async fn fetch(&self) -> Vec; } #[derive(Debug, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Source { Raw(Raw), Hosts(Hosts), Http(Http), Github(String), Sourcehut(String), Gitlab(String), Codeberg(String), } #[async_trait] impl Fetch for Source { async fn fetch(&self) -> Vec { match *self { Source::Raw(ref raw) => raw.fetch().await, Source::Hosts(ref raw) => raw.fetch().await, Source::Http(ref raw) => raw.fetch().await, Source::Github(ref user) => Http { url: format!("https://github.com/{user}.keys") }.fetch().await, Source::Sourcehut(ref user) => Http { url: format!("https://meta.sr.ht/{user}.keys") }.fetch().await, Source::Gitlab(ref user) => Http { url: format!("https://gitlab.com/{user}.keys") }.fetch().await, Source::Codeberg(ref user) => Http { url: format!("https://codeberg.org/{user}.keys") }.fetch().await, } } } #[derive(Debug, Deserialize)] pub struct Raw(Vec); #[async_trait::async_trait] impl Fetch for Raw { async fn fetch(&self) -> Vec { self.0.clone() } } #[derive(Debug, Deserialize)] pub struct Hosts(pub Vec); #[async_trait] impl Fetch for Hosts { async fn fetch(&self) -> Vec { // TODO: Check if we can do it in-process instead of shelling out to `ssh-keyscan` let result = Command::new("ssh-keyscan") .args(&self.0) .output() .await .unwrap(); std::str::from_utf8(&result.stdout) .unwrap() .trim() .split('\n') .map(|line| { // Ignore first column as it contain hostname which is not // needed there line.split(' ') .skip(1) .collect::>() .join(" ") .to_owned() }) .map(|k| PublicKey::from_openssh(&k).unwrap()) .collect() } } #[derive(Debug, Deserialize)] pub struct Http { pub url: String } #[async_trait] impl Fetch for Http { async fn fetch(&self) -> Vec { reqwest::get(&self.url) .await .unwrap() .text() .await .unwrap() .trim() .split('\n') .map(|s| PublicKey::from_openssh(s).unwrap()) .collect() } }