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}