Skip to content

Commit a90670d

Browse files
committed
feat(Config): convert between versions and expose it in the gen-cli
1 parent 329299e commit a90670d

File tree

9 files changed

+348
-97
lines changed

9 files changed

+348
-97
lines changed

Cargo.lock

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ thiserror = { workspace = true }
2525
tokio = { version = "1.12.0", features = [ "full" ] }
2626
hc_seed_bundle = "0.2.3"
2727
sodoken = "=0.0.11"
28+
enum-variants-strings = "0.3.0"
29+
strum = { version = "0.26.3", features = ["derive", "strum_macros"] }
2830

2931
[dependencies.argon2min]
3032
git = "https://github.com/Holo-Host/argon2min"
@@ -38,4 +40,4 @@ wasm-bindgen = ["rand/wasm-bindgen"]
3840
tokio = { version = "1.12.0", features = [ "full" ] }
3941
hc_seed_bundle = "0.2.3"
4042
sodoken = "=0.0.11"
41-
serde_json = "1.0.117"
43+
serde_json = "1.0.117"

core/src/config.rs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use arrayref::array_ref;
22
use ed25519_dalek::{Digest, Sha512, SigningKey, VerifyingKey};
3-
use failure::Error;
3+
use failure::{bail, Error};
44
use serde::*;
5+
use strum::{EnumDiscriminants, EnumString, VariantNames};
56

67
use crate::public_key;
78
pub const SEED_SIZE: usize = 32;
@@ -43,6 +44,7 @@ const ARGON2_ADDITIONAL_DATA: &[u8] = b"hpos-config admin ed25519 key v1";
4344

4445
pub type Seed = [u8; SEED_SIZE];
4546

47+
#[cfg_attr(test, derive(Clone, PartialEq))]
4648
#[derive(Debug, Deserialize, Serialize)]
4749
pub struct Admin {
4850
pub email: String,
@@ -53,12 +55,18 @@ pub struct Admin {
5355
pub public_key: VerifyingKey,
5456
}
5557

58+
#[cfg_attr(test, derive(Clone, PartialEq))]
5659
#[derive(Debug, Deserialize, Serialize)]
5760
pub struct Settings {
5861
pub admin: Admin,
5962
}
6063

61-
#[derive(Debug, Deserialize, Serialize)]
64+
#[cfg_attr(test, derive(Clone, PartialEq))]
65+
#[derive(Debug, Deserialize, Serialize, EnumDiscriminants)]
66+
#[strum_discriminants(
67+
derive(VariantNames, EnumString, strum::Display),
68+
strum(ascii_case_insensitive)
69+
)]
6270
pub enum Config {
6371
#[serde(rename = "v1")]
6472
V1 {
@@ -146,6 +154,58 @@ impl Config {
146154
| Config::V3 { settings, .. } => settings.admin.public_key,
147155
}
148156
}
157+
158+
/// Try to convert Config instance to the desired ConfigDiscriminants.
159+
/// As some conversions are impossible they will return an error accordingly.
160+
pub fn try_convert(self, desired: ConfigDiscriminants) -> Result<Self, Error> {
161+
match (self, desired) {
162+
(cfg @ Config::V1 { .. }, ConfigDiscriminants::V1) => Ok(cfg),
163+
(Config::V1 { .. }, ConfigDiscriminants::V2) => bail!("cannot convert V1 to V2"),
164+
(Config::V1 { .. }, ConfigDiscriminants::V3) => bail!("cannot convert V1 to V3"),
165+
(
166+
Config::V2 {
167+
device_bundle,
168+
derivation_path,
169+
registration_code,
170+
settings,
171+
},
172+
ConfigDiscriminants::V1,
173+
) => Ok(Config::V2 {
174+
device_bundle,
175+
derivation_path,
176+
registration_code,
177+
settings,
178+
}),
179+
(cfg @ Config::V2 { .. }, ConfigDiscriminants::V2) => Ok(cfg),
180+
(Config::V2 { .. }, ConfigDiscriminants::V3) => bail!("cannot convert V2 to V3"),
181+
(
182+
Config::V3 {
183+
// device_bundle,
184+
// settings,
185+
..
186+
},
187+
ConfigDiscriminants::V1,
188+
) => {
189+
unimplemented!("convert V3 to V1 (lossy)");
190+
}
191+
(
192+
Config::V3 {
193+
device_bundle,
194+
device_derivation_path,
195+
registration_code,
196+
settings,
197+
..
198+
},
199+
ConfigDiscriminants::V2,
200+
) => Ok(Config::V2 {
201+
device_bundle,
202+
derivation_path: device_derivation_path,
203+
registration_code,
204+
settings,
205+
}),
206+
(cfg @ Config::V3 { .. }, ConfigDiscriminants::V3) => Ok(cfg),
207+
}
208+
}
149209
}
150210

151211
// fn generate_keypair(
@@ -182,3 +242,56 @@ pub fn admin_keypair_from(
182242

183243
Ok(SigningKey::from_bytes(&hash))
184244
}
245+
246+
#[cfg(test)]
247+
mod tests {
248+
use crate::{test_utils::generate_test_hpos_config, Config};
249+
250+
#[tokio::test]
251+
async fn convert_v3_to_v2() {
252+
let (config, _) = generate_test_hpos_config().await.unwrap();
253+
254+
// ensure we start with a v3 config
255+
let v3 = config
256+
.clone()
257+
.try_convert(crate::config::ConfigDiscriminants::V3)
258+
.unwrap();
259+
assert_eq!(config, v3);
260+
261+
let v2 = config
262+
.clone()
263+
.try_convert(crate::config::ConfigDiscriminants::V2)
264+
.unwrap();
265+
266+
if let Config::V2 {
267+
device_bundle,
268+
derivation_path,
269+
registration_code,
270+
settings,
271+
} = v2
272+
{
273+
let v2_device_bundle = device_bundle;
274+
let v2_derivation_path = derivation_path;
275+
let v2_registration_code = registration_code;
276+
let v2_settings = settings;
277+
278+
if let Config::V3 {
279+
device_bundle,
280+
device_derivation_path,
281+
registration_code,
282+
settings,
283+
..
284+
} = config
285+
{
286+
assert_eq!(v2_device_bundle, device_bundle);
287+
assert_eq!(v2_derivation_path, device_derivation_path);
288+
assert_eq!(v2_registration_code, registration_code);
289+
assert_eq!(v2_settings, settings);
290+
} else {
291+
panic!("config isn't v3");
292+
};
293+
} else {
294+
panic!("must match v2");
295+
}
296+
}
297+
}

core/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ pub mod public_key;
33
pub mod types;
44
pub mod utils;
55

6+
#[cfg(test)]
7+
pub mod tests;
8+
9+
#[cfg(test)]
10+
pub mod test_utils;
11+
612
pub use config::{admin_keypair_from, Config};

core/src/test_utils.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use ed25519_dalek::VerifyingKey;
2+
use failure::Error;
3+
4+
use crate::Config;
5+
6+
/// Can be used akin to a test fixture
7+
pub(crate) async fn generate_test_hpos_config() -> Result<(Config, VerifyingKey), Error> {
8+
// emulate the UI
9+
10+
let master = hc_seed_bundle::UnlockedSeedBundle::new_random()
11+
.await
12+
.unwrap();
13+
14+
let passphrase = sodoken::BufRead::from(b"test-passphrase".to_vec());
15+
let revocation_bundle = master.derive(0).await.unwrap();
16+
let revocation_pub_key = revocation_bundle.get_sign_pub_key().read_lock().to_vec();
17+
18+
let device_derivation_path = 2;
19+
let device_bundle = master.derive(device_derivation_path).await.unwrap();
20+
let device_bundle_encoded_bytes = device_bundle
21+
.lock()
22+
.add_pwhash_cipher(passphrase)
23+
.lock()
24+
.await
25+
.unwrap();
26+
let device_bundle_base64 = base64::encode(&device_bundle_encoded_bytes);
27+
28+
// derive the holoport ID
29+
30+
let holoport_id = device_bundle.derive(1).await.unwrap();
31+
32+
let holoport_id = holoport_id.get_sign_pub_key().read_lock().to_vec();
33+
34+
// initialize a new Config struct
35+
let email = "joel@holo.host".to_string();
36+
let password = "password".to_string();
37+
let registration_code = "registration-code".to_string();
38+
let rev_key_bytes = revocation_pub_key[0..32].try_into().unwrap();
39+
let revocation_pub_key = VerifyingKey::from_bytes(&rev_key_bytes).unwrap();
40+
let holoport_id_bytes = holoport_id[0..32].try_into().unwrap();
41+
let holoport_id = VerifyingKey::from_bytes(&holoport_id_bytes).unwrap();
42+
43+
Config::new(
44+
// email: String,
45+
email.clone(),
46+
// password: String,
47+
password,
48+
// registration_code: String,
49+
registration_code,
50+
// revocation_pub_key: VerifyingKey,
51+
revocation_pub_key,
52+
// derivation_path: String,
53+
device_derivation_path.to_string(),
54+
// device_bundle: String,
55+
device_bundle_base64.clone(),
56+
// device_pub_key: VerifyingKey,
57+
holoport_id,
58+
)
59+
}

0 commit comments

Comments
 (0)