kassandra_client/
config.rs

1//! Module for handling the backing config file of the client. The
2//! purpose of the config is to persist information about which
3//! keys are registered to which services.
4
5use std::collections::BTreeMap;
6use std::collections::btree_map::Entry;
7use std::io::ErrorKind;
8use std::path::Path;
9
10use fmd::KeyExpansion;
11use fmd::fmd2_compact::{CompactSecretKey, MultiFmd2CompactScheme};
12use serde::{Deserialize, Serialize};
13use sha2::Digest;
14use shared::db::EncKey;
15
16use crate::error::{self, Error};
17
18/// The name of the config file
19pub const CLIENT_FILE_NAME: &str = "kassandra-client.toml";
20
21#[derive(Debug, Default, Clone, Serialize, Deserialize)]
22pub struct Config {
23    /// A map from the hash of FMD secret key to the services
24    /// it is registered with
25    pub services: BTreeMap<String, Vec<Service>>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29/// A service instance
30pub struct Service {
31    /// Address of the service
32    pub url: String,
33    /// An index indication which share of fmd keys it received
34    pub index: usize,
35    /// The key used to decrypt responses from the service
36    pub enc_key: EncKey,
37}
38
39impl Config {
40    /// Load the config from the specified path
41    pub fn load(path: impl AsRef<Path>) -> error::Result<Self> {
42        toml::from_str(&std::fs::read_to_string(path).map_err(Error::Io)?).map_err(|e| {
43            Error::Io(std::io::Error::new(
44                ErrorKind::InvalidData,
45                format!("Could not parse client config file: {e}"),
46            ))
47        })
48    }
49
50    /// Load a config if it exists, otherwise create a new one
51    pub fn load_or_new(path: impl AsRef<Path>) -> error::Result<Self> {
52        let path = path.as_ref().join(CLIENT_FILE_NAME);
53        if path.exists() {
54            Self::load(path)
55        } else {
56            Ok(Self::default())
57        }
58    }
59
60    /// Save the config at the specified path
61    pub fn save(&mut self, path: impl AsRef<Path>) -> error::Result<()> {
62        for (_, services) in self.services.iter_mut() {
63            services.sort_by_key(|s| s.index);
64            services.dedup_by_key(|s| s.index);
65        }
66        let dest = path.as_ref().join(CLIENT_FILE_NAME);
67        std::fs::write(
68            dest,
69            toml::to_string(&self).expect("This operation should not fail"),
70        )
71        .map_err(Error::Io)
72    }
73
74    /// Add a new service which a specified key will be registered to.
75    pub fn add_service(&mut self, key: String, url: &str, enc_key: EncKey) {
76        match self.services.entry(key) {
77            Entry::Vacant(e) => {
78                e.insert(vec![Service {
79                    url: url.to_string(),
80                    index: 1,
81                    enc_key,
82                }]);
83            }
84            Entry::Occupied(mut o) => {
85                let ix = o.get().iter().map(|s| s.index).max().unwrap_or_default();
86                o.get_mut().push(Service {
87                    url: url.to_string(),
88                    index: ix + 1,
89                    enc_key,
90                });
91            }
92        }
93    }
94
95    /// Get the services that the specified key is configured to be registered to
96    pub fn get_services(&self, key: &String) -> Vec<Service> {
97        self.services.get(key).cloned().unwrap_or_default()
98    }
99}
100
101/// Get a hash of an FMD key from a Compact secret key and choice of gamma.
102pub fn hash_key(csk_key: &CompactSecretKey, gamma: usize) -> String {
103    let mut hasher = sha2::Sha256::new();
104
105    let cpk_key = csk_key.master_public_key();
106    let mut scheme = MultiFmd2CompactScheme::new(gamma, 1);
107    let (fmd_key, _) = scheme.expand_keypair(csk_key, &cpk_key);
108
109    hasher.update(serde_json::to_string(&fmd_key).unwrap().as_bytes());
110    let bytes: [u8; 32] = hasher.finalize().into();
111    hex::encode(bytes)
112}