1use anyhow::{Context as _, Result, anyhow};
2use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD as base64engine};
3use orion::aead::{self, SecretKey};
4use std::{
5 env,
6 fs::{self, File},
7 io::{BufRead as _, BufReader},
8};
9
10pub struct WorkgroupKey(SecretKey);
11
12impl WorkgroupKey {
13 pub fn load() -> Result<Self> {
14 Ok(WorkgroupKey(SecretKey::from_slice(
15 &base64engine.decode(
16 &BufReader::new(File::open(format!(
17 "{}/.ssh/workgroup",
18 env::var("HOME").unwrap_or_default()
19 ))?)
20 .lines()
21 .next()
22 .context("Workgroup key file is corrupted")??,
23 )?,
24 )?))
25 }
26
27 pub fn create() -> Result<()> {
28 Ok(fs::write(
29 format!("{}/.ssh/workgroup", env::var("HOME").unwrap_or_default()),
30 base64engine.encode(SecretKey::default().unprotected_as_bytes()),
31 )?)
32 }
33}
34
35pub struct SshChain(pub Vec<String>);
36
37impl SshChain {
38 fn open_impl(key: &WorkgroupKey) -> Result<Vec<String>> {
39 Ok(String::from_utf8(aead::open(
40 &key.0,
41 &base64engine.decode(env::var("WORKGROUP_CHAIN")?)?,
42 )?)?
43 .split_whitespace()
44 .map(ToOwned::to_owned)
45 .collect::<Vec<_>>())
46 }
47
48 pub fn open(key: Option<&WorkgroupKey>) -> SshChain {
49 let ssh_chain = key
50 .context("No workgroup key passed")
51 .and_then(Self::open_impl)
52 .and_then(|chain| {
53 if chain.is_empty() {
54 Err(anyhow!("Empty ssh chain, but decoded"))
55 } else {
56 Ok(chain)
57 }
58 });
59
60 SshChain(match (ssh_chain, env::var("SSH_CONNECTION")) {
61 (Err(_), Err(_)) => vec![],
62 (Err(_), Ok(conn)) => vec![conn.split_whitespace().next().unwrap_or("?").to_owned()],
63 (Ok(ch), _) => ch,
64 })
65 }
66
67 fn seal_impl(&self, key: &WorkgroupKey) -> Result<String> {
68 Ok(base64engine.encode(aead::seal(&key.0, self.0.join(" ").as_bytes())?))
69 }
70
71 #[must_use]
72 pub fn seal(&self, key: &WorkgroupKey) -> String {
73 self.seal_impl(key).unwrap_or_default()
74 }
75}