Skip to content

Commit 46bff98

Browse files
author
Alexander Weber
committed
archive versioning
1 parent 8c32092 commit 46bff98

4 files changed

Lines changed: 138 additions & 125 deletions

File tree

src/archive.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1+
use anyhow::Result;
2+
13
pub const VERSION_ID_LEN: usize = 36;
24
pub const VERSION_0_1: &'static str = "9f1e0683-7655-4f73-940a-38fa580b5725";
35

4-
pub mod v1 {
6+
pub fn split_version_and_data(data: &Vec<u8>) -> Result<(String, &[u8])> {
7+
Ok((
8+
String::from_utf8(data[0..VERSION_ID_LEN].to_vec())?,
9+
&data[VERSION_ID_LEN..],
10+
))
11+
}
12+
13+
pub(crate) mod v1 {
514
/// The archive that describes the single file storaing all information.
615
#[derive(Debug, serde::Serialize, serde::Deserialize)]
716
pub(crate) struct Archive {

src/engine.rs

Lines changed: 125 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,141 @@
1-
use {
2-
crate::{
3-
archive::{
4-
v1,
5-
VERSION_0_1,
6-
VERSION_ID_LEN,
1+
pub(crate) mod v1 {
2+
use {
3+
crate::{
4+
archive,
5+
archive::{
6+
v1,
7+
VERSION_0_1,
8+
},
9+
blueprint::Blueprint,
10+
error::Error,
711
},
8-
blueprint::Blueprint,
9-
error::Error,
10-
},
11-
anyhow::Result,
12-
argon2::{
13-
password_hash::rand_core::{
14-
OsRng,
15-
RngCore,
12+
anyhow::Result,
13+
argon2::{
14+
password_hash::rand_core::{
15+
OsRng,
16+
RngCore,
17+
},
18+
PasswordHasher,
19+
PasswordVerifier,
1620
},
17-
PasswordHasher,
18-
PasswordVerifier,
19-
},
20-
base64::{
21-
engine::general_purpose::STANDARD,
22-
Engine,
23-
},
24-
ssss::SsssConfig,
25-
std::fs,
26-
uuid::Uuid,
27-
};
28-
29-
pub(crate) struct SSS<'x> {
30-
pub argon: argon2::Argon2<'x>,
31-
}
21+
base64::{
22+
engine::general_purpose::STANDARD,
23+
Engine,
24+
},
25+
ssss::SsssConfig,
26+
std::fs,
27+
uuid::Uuid,
28+
};
3229

33-
impl<'x> SSS<'x> {
34-
pub fn new() -> Self {
35-
Self {
36-
argon: argon2::Argon2::new(
37-
argon2::Algorithm::Argon2id,
38-
argon2::Version::V0x13,
39-
argon2::Params::new(2048, 32, 64, None).unwrap(),
40-
),
41-
}
30+
pub(crate) struct SSS<'x> {
31+
pub argon: argon2::Argon2<'x>,
4232
}
4333

44-
pub async fn generate(&self, secret_data: &Vec<u8>, blueprint: &Blueprint, trust: bool) -> Result<()> {
45-
let shares = ssss::gen_shares(
46-
&SsssConfig::default()
47-
.set_max_secret_size(secret_data.len())
48-
.set_num_shares(blueprint.generate.len() as u8)
49-
.set_threshold(blueprint.threshold as u8),
50-
&secret_data,
51-
)?;
34+
impl<'x> SSS<'x> {
35+
pub fn new() -> Self {
36+
Self {
37+
argon: argon2::Argon2::new(
38+
argon2::Algorithm::Argon2id,
39+
argon2::Version::V0x13,
40+
argon2::Params::new(2048, 32, 64, None).unwrap(),
41+
),
42+
}
43+
}
5244

53-
for z in blueprint.generate.iter().zip(shares) {
54-
let share_data = v1::Archive {
55-
uid: Uuid::new_v4().hyphenated().to_string(),
56-
name: z.0.name.clone(),
57-
comment: z.0.comment.clone(),
58-
info: if z.0.info.unwrap_or(false) {
59-
Some(v1::SecretInfo {
60-
num_shares: blueprint.generate.len(),
61-
threshold: blueprint.threshold,
62-
})
63-
} else {
64-
None
65-
},
66-
share: match &z.0.encrypt {
67-
| Some(enc) => {
68-
let mut salt = [0u8; 32];
69-
OsRng::default().fill_bytes(&mut salt);
70-
let pass = enc.exec(trust)?;
71-
let hash = self
72-
.argon
73-
.hash_password(
74-
pass.as_bytes(),
75-
argon2::password_hash::SaltString::encode_b64(&salt).unwrap().as_salt(),
76-
)
77-
.unwrap()
78-
.serialize()
79-
.to_string();
45+
pub async fn generate(&self, secret_data: &Vec<u8>, blueprint: &Blueprint, trust: bool) -> Result<()> {
46+
let shares = ssss::gen_shares(
47+
&SsssConfig::default()
48+
.set_max_secret_size(secret_data.len())
49+
.set_num_shares(blueprint.generate.len() as u8)
50+
.set_threshold(blueprint.threshold as u8),
51+
&secret_data,
52+
)?;
8053

81-
v1::Share::EncryptedBase64 {
82-
data: STANDARD.encode(simplecrypt::encrypt(z.1.as_bytes(), pass.as_bytes())),
83-
hash: v1::Hash::Argon2id(hash),
84-
}
85-
},
86-
| None => {
87-
let encoded_share = STANDARD.encode(z.1);
88-
v1::Share::PlainBase64(encoded_share)
54+
for z in blueprint.generate.iter().zip(shares) {
55+
let share_data = v1::Archive {
56+
uid: Uuid::new_v4().hyphenated().to_string(),
57+
name: z.0.name.clone(),
58+
comment: z.0.comment.clone(),
59+
info: if z.0.info.unwrap_or(false) {
60+
Some(v1::SecretInfo {
61+
num_shares: blueprint.generate.len(),
62+
threshold: blueprint.threshold,
63+
})
64+
} else {
65+
None
8966
},
90-
},
91-
};
92-
let share_data_str = STANDARD.encode(serde_yaml::to_string(&share_data)?);
67+
share: match &z.0.encrypt {
68+
| Some(enc) => {
69+
let mut salt = [0u8; 32];
70+
OsRng::default().fill_bytes(&mut salt);
71+
let pass = enc.exec(trust)?;
72+
let hash = self
73+
.argon
74+
.hash_password(
75+
pass.as_bytes(),
76+
argon2::password_hash::SaltString::encode_b64(&salt).unwrap().as_salt(),
77+
)
78+
.unwrap()
79+
.serialize()
80+
.to_string();
9381

94-
fs::write(&z.0.path, format!("{}{}", VERSION_0_1, share_data_str))?;
95-
}
96-
Ok(())
97-
}
98-
99-
pub async fn restore(&self, shares: &Vec<(String, Vec<u8>)>) -> Result<Vec<u8>> {
100-
let mut share_data = Vec::<String>::new();
101-
for s in shares {
102-
let version = String::from_utf8(s.1[0..VERSION_ID_LEN].to_vec())?;
103-
let data = &s.1[VERSION_ID_LEN..];
104-
match version.as_str() {
105-
| VERSION_0_1 => {
106-
let archive = serde_yaml::from_str::<v1::Archive>(&String::from_utf8(STANDARD.decode(&data)?)?)?;
107-
let data = match archive.share {
108-
| v1::Share::PlainBase64(v) => STANDARD.decode(v)?,
109-
| v1::Share::EncryptedBase64 { hash, data } => {
110-
let pw: String = dialoguer::Password::new()
111-
.with_prompt(format!(
112-
"Enter password for share (path: {}, name: {})",
113-
&s.0,
114-
archive.name.unwrap_or("{unknown}".to_owned())
115-
))
116-
.interact()?;
117-
match hash {
118-
| v1::Hash::Argon2id(v) => {
119-
let pw_hash = argon2::PasswordHash::new(&v).or(Err(Error::PasswordVerification))?;
120-
self.argon
121-
.verify_password(pw.as_bytes(), &pw_hash)
122-
.or(Err(Error::PasswordVerification))?;
123-
},
82+
v1::Share::EncryptedBase64 {
83+
data: STANDARD.encode(simplecrypt::encrypt(z.1.as_bytes(), pass.as_bytes())),
84+
hash: v1::Hash::Argon2id(hash),
12485
}
125-
let data_dec = STANDARD.decode(data)?;
126-
simplecrypt::decrypt(data_dec.as_slice(), pw.as_bytes())?
12786
},
128-
};
129-
share_data.push(String::from_utf8(data)?);
130-
},
131-
| v => Err(Error::UnknownVersion(v.to_owned()))?,
87+
| None => {
88+
let encoded_share = STANDARD.encode(z.1);
89+
v1::Share::PlainBase64(encoded_share)
90+
},
91+
},
92+
};
93+
let share_data_str = STANDARD.encode(serde_yaml::to_string(&share_data)?);
94+
95+
fs::write(&z.0.path, format!("{}{}", VERSION_0_1, share_data_str))?;
13296
}
97+
Ok(())
13398
}
13499

135-
Ok(ssss::unlock(share_data.as_slice())?)
100+
pub async fn restore(&self, shares: &Vec<(String, Vec<u8>)>) -> Result<Vec<u8>> {
101+
let mut share_data = Vec::<String>::new();
102+
for s in shares {
103+
let version_data = archive::split_version_and_data(&s.1)?;
104+
match version_data.0.as_str() {
105+
| VERSION_0_1 => {
106+
let archive = serde_yaml::from_str::<v1::Archive>(&String::from_utf8(
107+
STANDARD.decode(&version_data.1)?,
108+
)?)?;
109+
let data = match archive.share {
110+
| v1::Share::PlainBase64(v) => STANDARD.decode(v)?,
111+
| v1::Share::EncryptedBase64 { hash, data } => {
112+
let pw: String = dialoguer::Password::new()
113+
.with_prompt(format!(
114+
"Enter password for share (path: {}, name: {})",
115+
&s.0,
116+
archive.name.unwrap_or("{unknown}".to_owned())
117+
))
118+
.interact()?;
119+
match hash {
120+
| v1::Hash::Argon2id(v) => {
121+
let pw_hash =
122+
argon2::PasswordHash::new(&v).or(Err(Error::PasswordVerification))?;
123+
self.argon
124+
.verify_password(pw.as_bytes(), &pw_hash)
125+
.or(Err(Error::PasswordVerification))?;
126+
},
127+
}
128+
let data_dec = STANDARD.decode(data)?;
129+
simplecrypt::decrypt(data_dec.as_slice(), pw.as_bytes())?
130+
},
131+
};
132+
share_data.push(String::from_utf8(data)?);
133+
},
134+
| v => Err(Error::UnknownVersion(v.to_owned()))?,
135+
}
136+
}
137+
138+
Ok(ssss::unlock(share_data.as_slice())?)
139+
}
136140
}
137141
}

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use {
22
crate::{
33
blueprint::Blueprint,
4-
engine::SSS,
4+
engine::v1::SSS,
55
},
66
anyhow::Result,
77
args::{

test/blueprint.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ generate:
33
- path: ./test/alice.share
44
- path: ./test/bob.share
55
name: bob
6-
encrypt: !plain boosker1
6+
encrypt: !plain example-bob
77
info: true
88
comment: boosker
99
- path: ./test/charlie.share
1010
name: charlie
11-
encrypt: !shell printf boosker1
11+
encrypt: !shell printf example-charlie

0 commit comments

Comments
 (0)