keystore_rs/
os.rs

1use anyhow::{Context, Result};
2use ed25519_consensus::SigningKey;
3
4#[cfg(target_os = "macos")]
5use security_framework::os::macos::keychain::SecKeychain;
6
7#[cfg(target_os = "linux")]
8use base64::{engine::general_purpose, Engine as _};
9#[cfg(target_os = "linux")]
10use keyring::Entry;
11
12use crate::{create_signing_key, KeyStore};
13
14pub struct KeyChain;
15impl KeyStore for KeyChain {
16    fn add_signing_key(&self, id: &str, signing_key: &SigningKey) -> Result<()> {
17        add_signing_key_to_keychain(id, signing_key).context(format!(
18            "failed to store signing key for id {} in keychain",
19            id
20        ))
21    }
22
23    fn get_signing_key(&self, id: &str) -> Result<SigningKey> {
24        get_signing_key_from_keychain(id).context(format!(
25            "failed to load signing key for id {} from keychain",
26            id
27        ))
28    }
29
30    fn get_or_create_signing_key(&self, id: &str) -> Result<SigningKey> {
31        match self.get_signing_key(id) {
32            Ok(key) => Ok(key),
33            Err(_) => {
34                let new_key = create_signing_key();
35                self.add_signing_key(id, &new_key).with_context(|| {
36                    format!("Failed to create and store new key for id: {}", id)
37                })?;
38                Ok(new_key)
39            }
40        }
41    }
42}
43
44#[cfg(target_os = "macos")]
45pub fn add_signing_key_to_keychain(
46    id: &str,
47    signing_key: &SigningKey,
48) -> Result<(), security_framework::base::Error> {
49    let signing_key_bytes = signing_key.to_bytes();
50
51    let keychain = SecKeychain::default()?;
52    keychain.add_generic_password(id, "signing_key", &signing_key_bytes)
53}
54
55#[cfg(target_os = "linux")]
56pub fn add_signing_key_to_keychain(id: &str, signing_key: &SigningKey) -> Result<()> {
57    let signing_key_bytes = signing_key.to_bytes();
58    let signing_key_str = general_purpose::STANDARD.encode(signing_key_bytes);
59
60    let entry = Entry::new(id, "signing_key").context("failed to create new keyring entry")?;
61    entry.set_password(&signing_key_str)?;
62
63    Ok(())
64}
65
66#[cfg(target_os = "macos")]
67pub fn get_signing_key_from_keychain(id: &str) -> Result<SigningKey> {
68    let keychain = SecKeychain::default()?;
69
70    let (signing_key_bytes, _) = keychain
71        .find_generic_password(id, "signing_key")
72        .context(format!("Failed to find signing key for id: {}", id))?;
73
74    let mut signing_key_array = [0u8; 32];
75    signing_key_array.copy_from_slice(&signing_key_bytes[..32]);
76    Ok(SigningKey::from(signing_key_array))
77}
78
79#[cfg(target_os = "linux")]
80pub fn get_signing_key_from_keychain(id: &str) -> Result<SigningKey> {
81    let keyring = Entry::new(id, "signing_key")?;
82
83    let signing_key_str = keyring
84        .get_password()
85        .context("failed to get password from keyring")?;
86
87    let signing_key_bytes = general_purpose::STANDARD.decode(signing_key_str)?;
88    let mut signing_key_array = [0u8; 32];
89    signing_key_array.copy_from_slice(&signing_key_bytes[..32]);
90    Ok(SigningKey::from(signing_key_array))
91}