// SPDX-FileCopyrightText: 2024 Łukasz Niemier <#@hauleth.dev> // SPDX-FileCopyrightText: 2025 Łukasz Niemier <#@hauleth.dev> // // SPDX-License-Identifier: EUPL-1.2 use serde::Deserialize; use ssh_key::PublicKey; use std::process::Command; mod atproto; mod helpers; pub use atproto::ATProto; pub trait Fetch: std::fmt::Debug { fn fetch(&self) -> Vec; } #[derive(Debug, Deserialize)] #[serde(rename_all = "lowercase")] #[non_exhaustive] pub enum Source { Raw(Raw), Hosts(Hosts), Http(Http), Github(String), Sourcehut(String), Gitlab(String), Codeberg(String), #[serde(deserialize_with = "helpers::string_or_struct")] Tangled(ATProto), } impl Fetch for Source { fn fetch(&self) -> Vec { match *self { Source::Raw(ref raw) => raw.fetch(), Source::Hosts(ref raw) => raw.fetch(), Source::Http(ref raw) => raw.fetch(), Source::Github(ref user) => Http { url: format!("https://github.com/{user}.keys"), } .fetch(), Source::Sourcehut(ref user) => Http { url: format!("https://meta.sr.ht/{user}.keys"), } .fetch(), Source::Gitlab(ref user) => Http { url: format!("https://gitlab.com/{user}.keys"), } .fetch(), Source::Codeberg(ref user) => Http { url: format!("https://codeberg.org/{user}.keys"), } .fetch(), Source::Tangled(ref atproto) => atproto.fetch(), } } } #[derive(Debug, Deserialize)] pub struct Raw(Box<[PublicKey]>); impl Fetch for Raw { fn fetch(&self) -> Vec { self.0.clone().into() } } #[derive(Debug, Deserialize)] pub struct Hosts(pub Box<[String]>); impl Fetch for Hosts { 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().unwrap(); std::str::from_utf8(&result.stdout) .unwrap() .trim() .split('\n') .map(str::trim) // Remove comments .filter(|line| !line.starts_with("#")) // Ignore first column as it contain hostname which is not // needed there .map(|line| line.split_once(' ').unwrap().1) .map(|k| PublicKey::from_openssh(&k).unwrap()) .collect() } } #[derive(Debug, Deserialize)] pub struct Http { pub url: String, } impl Fetch for Http { fn fetch(&self) -> Vec { ureq::get(&self.url) .call() .unwrap() .body_mut() .read_to_string() .unwrap() .trim() .split('\n') .map(|s| PublicKey::from_openssh(s).unwrap()) .collect() } }