Add `yama keyring create` command

This commit is contained in:
Olivier 'reivilibre' 2023-08-08 21:17:27 +01:00
parent 53886aad46
commit 1ac9bb6d8d
9 changed files with 297 additions and 45 deletions

View File

@ -20,6 +20,7 @@ use eyre::{bail, eyre, Context, ContextCompat};
use indicatif::ProgressStyle;
use patricia_tree::PatriciaMap;
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::iter::Iterator;
use std::path::{Path, PathBuf};
use std::str::FromStr;
@ -35,8 +36,10 @@ use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::Layer;
use users::{get_current_gid, get_current_uid};
use yama::extract::flatten_treenode;
use yama::init::{generate_master_keyring, pack_keyring};
use yama::open::{open_keyring_interactive, open_pile, pre_open_keyring, update_cache};
use yama::init::pack_keyring;
use yama::open::{
open_keyring_interactive, open_pile, pre_open_keyring, pre_open_keyring_at_path, update_cache,
};
use yama::pile_connector::PileConnectionScheme;
use yama::scan::create_uidgid_lookup_tables;
use yama::storing::{
@ -53,6 +56,7 @@ use yama_midlevel_crypto::chunk_id::ChunkIdKey;
use yama_pile::definitions::{
IndexBloblogEntry, PackedPileConfig, PileConfig, RecursiveChunkRef, SUPPORTED_YAMA_PILE_VERSION,
};
use yama_pile::keyring::{Keyring, INDIVIDUAL_KEYS, KEY_GROUPS};
use yama_pile::locks::LockKind;
use yama_pile::pointers::Pointer;
use yama_pile::tree::unpopulated::ScanEntry;
@ -331,7 +335,7 @@ async fn main() -> eyre::Result<()> {
Some(line.trim().to_owned())
};
let master_keyring = generate_master_keyring();
let master_keyring = Keyring::generate_new_master();
let master_key_packed = pack_keyring(
master_keyring.clone(),
master_password.as_ref().map(|s| s.as_ref()),
@ -909,7 +913,72 @@ async fn main() -> eyre::Result<()> {
.close()
.await?;
}
_other => todo!(),
YamaCommand::Keyring(KeyringCommand::Inspect { file }) => {
let keyring = pre_open_keyring_at_path(&file).await?;
let _keyring = open_keyring_interactive(keyring).await?;
todo!();
}
YamaCommand::Keyring(KeyringCommand::Create {
new,
from,
with,
no_password,
weak,
}) => {
let mut keys_to_copy = BTreeSet::new();
if weak {
unimplemented!("can't generate weak keys yet: unimplemented.");
}
if new.exists() {
bail!("{new:?} already exists; won't overwrite!");
}
for key in with.split(",") {
if let Some((_, key_group)) = KEY_GROUPS.iter().find(|(k, _)| *k == key) {
keys_to_copy.extend(key_group.into_iter());
} else if INDIVIDUAL_KEYS.contains(&key) {
keys_to_copy.insert(key);
} else {
bail!("Not a known key or key group: {key:?}");
}
}
let keyring = match &from {
Some(path) => pre_open_keyring_at_path(path).await?,
None => pre_open_keyring(Path::new(".")).await?,
};
let keyring = open_keyring_interactive(keyring).await?;
let new_keyring = keyring
.partial_copy(&keys_to_copy)
.context("failed to make partial copy of keyring")?;
let new_keyring_password = if no_password {
warn!("Not setting a new keyring password. The new keyring will be unprotected.");
None
} else {
println!("enter new keyring password:");
let stdin = stdin();
let mut stdin_br = BufReader::new(stdin);
let mut line = String::new();
stdin_br.read_line(&mut line).await?;
Some(line.trim().to_owned())
};
let new_keyring_packed = pack_keyring(
new_keyring,
new_keyring_password.as_ref().map(|s| s.as_ref()),
)?;
tokio::fs::write(&new, &new_keyring_packed.into_byte_vec())
.await
.context("couldn't write to new keyring file")?;
info!("new keyring created!");
}
}
Ok(())

View File

@ -5,7 +5,7 @@ use yama_midlevel_crypto::byte_layer::{ByteLayer, CborSerde};
use yama_midlevel_crypto::key_derivation::KeyDerivationParameters;
use yama_midlevel_crypto::sym_box::SymBox;
use yama_pile::definitions::{PackedKeyring, PackedPileConfig, UnlockedOrLockedKeyring};
use yama_pile::keyring::{generate_r_w_keys, Keyring};
use yama_pile::keyring::Keyring;
use yama_pile::{DIR_BLOBLOGS, DIR_INDICES, DIR_LOCKS, FILE_MASTER_KEYRING, FILE_YAMA_CONFIG};
use yama_wormfile::paths::WormPath;
use yama_wormfile::{WormFileProvider, WormFileWriter};
@ -71,26 +71,6 @@ pub async fn init_pile(
Ok(())
}
pub fn generate_master_keyring() -> Keyring {
let (r_config, w_config) = generate_r_w_keys();
let (r_bloblog_footer, w_bloblog_footer) = generate_r_w_keys();
let (r_bloblog_contents, w_bloblog_contents) = generate_r_w_keys();
let (r_locks, w_locks) = generate_r_w_keys();
let (r_pointer, w_pointer) = generate_r_w_keys();
Keyring {
r_config: Some(r_config),
w_config: Some(w_config),
r_bloblog_footer: Some(r_bloblog_footer),
w_bloblog_footer: Some(w_bloblog_footer),
r_bloblog_contents: Some(r_bloblog_contents),
w_bloblog_contents: Some(w_bloblog_contents),
r_locks: Some(r_locks),
w_locks: Some(w_locks),
r_pointer: Some(r_pointer),
w_pointer: Some(w_pointer),
}
}
// todo move this
pub fn pack_keyring(unpacked: Keyring, password: Option<&str>) -> eyre::Result<PackedKeyring> {
let packed = if let Some(password) = password {

View File

@ -22,15 +22,7 @@ pub async fn pre_open_keyring(connector_in_dir: &Path) -> eyre::Result<UnlockedO
for lookup in KEYRING_LOOKUP_SEQ {
let keyring_path = connector_in_dir.join(lookup);
if keyring_path.exists() {
let packed_keyring_bytes = tokio::fs::read(&keyring_path)
.await
.with_context(|| format!("failed to read keyring file at {:?}", keyring_path))?;
let packed_keyring = PackedKeyring::from_byte_vec(packed_keyring_bytes)
.deserialise()
.with_context(|| {
format!("failed to deserialise keyring file at {:?}", keyring_path)
})?;
return Ok(packed_keyring);
return pre_open_keyring_at_path(&keyring_path).await;
}
}
@ -41,6 +33,18 @@ pub async fn pre_open_keyring(connector_in_dir: &Path) -> eyre::Result<UnlockedO
);
}
pub async fn pre_open_keyring_at_path(
keyring_path: &Path,
) -> eyre::Result<UnlockedOrLockedKeyring> {
let packed_keyring_bytes = tokio::fs::read(&keyring_path)
.await
.with_context(|| format!("failed to read keyring file at {:?}", keyring_path))?;
let packed_keyring = PackedKeyring::from_byte_vec(packed_keyring_bytes)
.deserialise()
.with_context(|| format!("failed to deserialise keyring file at {:?}", keyring_path))?;
return Ok(packed_keyring);
}
pub async fn open_keyring_interactive(input: UnlockedOrLockedKeyring) -> eyre::Result<Keyring> {
match input {
UnlockedOrLockedKeyring::Locked { deriver, lockbox } => {

View File

@ -13,13 +13,13 @@ pub struct AsymKeyExchange<'bytes> {
inner: Cow<'bytes, [u8]>,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct EncryptingKey {
x25519: X25519PublicKey,
kyber: KyberPublicKey,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct DecryptingKey {
x25519: X25519PrivateKey,
kyber: KyberPrivateKey,

View File

@ -19,11 +19,23 @@ pub struct SigningKey {
ed25519: Ed25519PrivateKey,
}
impl PartialEq for SigningKey {
fn eq(&self, other: &Self) -> bool {
self.ed25519.to_keypair_bytes() == other.ed25519.to_keypair_bytes()
}
}
#[derive(Clone)]
pub struct VerifyingKey {
ed25519: Ed25519PublicKey,
}
impl PartialEq for VerifyingKey {
fn eq(&self, other: &Self) -> bool {
self.ed25519.as_bytes() == other.ed25519.as_bytes()
}
}
pub fn asym_signing_keypair() -> (SigningKey, VerifyingKey) {
let mut rng = thread_rng();
let keypair = ed25519_dalek::SigningKey::generate(&mut rng);

View File

@ -4,12 +4,12 @@ use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::Cow;
#[derive(Clone)]
#[derive(Clone, Eq, PartialEq)]
pub struct KyberPublicKey {
inner: pqc_kyber::PublicKey,
}
#[derive(Clone)]
#[derive(Clone, Eq, PartialEq)]
pub struct KyberPrivateKey {
inner: pqc_kyber::SecretKey,
}

View File

@ -9,7 +9,13 @@ pub struct X25519PrivateKey {
pub(crate) inner: x25519_dalek::StaticSecret,
}
#[derive(Clone)]
impl PartialEq for X25519PrivateKey {
fn eq(&self, other: &Self) -> bool {
self.inner.as_bytes() == other.inner.as_bytes()
}
}
#[derive(Clone, PartialEq)]
#[repr(transparent)]
pub struct X25519PublicKey {
pub(crate) inner: x25519_dalek::PublicKey,

View File

@ -15,7 +15,7 @@ rand = "0.8.5"
patricia_tree = "0.5.7"
hex = "0.4.3"
tokio = { version = "1.27.0", features = ["io-util", "macros"] }
tokio = { version = "1.27.0", features = ["io-util", "macros", "sync", "rt", "time"] }
serde = { version = "1.0.159", features = ["derive", "rc"] }
chrono = { version = "0.4.24", features = ["serde"] }

View File

@ -1,10 +1,47 @@
use eyre::ContextCompat;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use yama_midlevel_crypto::asym_box::AsymBox;
use yama_midlevel_crypto::asym_keyx::{generate_asym_keypair, DecryptingKey, EncryptingKey};
use yama_midlevel_crypto::asym_signed::{asym_signing_keypair, SigningKey, VerifyingKey};
use yama_midlevel_crypto::byte_layer::ByteLayer;
#[derive(Clone, Serialize, Deserialize)]
/// Exhaustive list of all keys
pub const INDIVIDUAL_KEYS: &[&str] = &[
"r_config",
"w_config",
"r_bloblog_footer",
"w_bloblog_footer",
"r_bloblog_contents",
"w_bloblog_contents",
"r_locks",
"w_locks",
"r_pointer",
"w_pointer",
];
/// Opinionated named groups of keys intended for specific purposes.
/// ALL: all keys
/// BACKUP:
pub const KEY_GROUPS: &[(&str, &[&str])] = &[
("ALL", INDIVIDUAL_KEYS),
(
"BACKUP",
&[
"r_config",
"r_bloblog_footer",
"w_bloblog_footer",
"w_bloblog_contents",
"r_locks",
"w_locks",
"r_pointer",
"w_pointer",
],
),
// TODO add more key groups... ("RESTORE", &[])
];
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct Keyring {
pub r_config: Option<ReaderKey>,
pub w_config: Option<WriterKey>,
@ -22,6 +59,133 @@ pub struct Keyring {
pub w_pointer: Option<WriterKey>,
}
impl Keyring {
pub fn generate_new_master() -> Self {
let (r_config, w_config) = generate_r_w_keys();
let (r_bloblog_footer, w_bloblog_footer) = generate_r_w_keys();
let (r_bloblog_contents, w_bloblog_contents) = generate_r_w_keys();
let (r_locks, w_locks) = generate_r_w_keys();
let (r_pointer, w_pointer) = generate_r_w_keys();
Keyring {
r_config: Some(r_config),
w_config: Some(w_config),
r_bloblog_footer: Some(r_bloblog_footer),
w_bloblog_footer: Some(w_bloblog_footer),
r_bloblog_contents: Some(r_bloblog_contents),
w_bloblog_contents: Some(w_bloblog_contents),
r_locks: Some(r_locks),
w_locks: Some(w_locks),
r_pointer: Some(r_pointer),
w_pointer: Some(w_pointer),
}
}
pub fn partial_copy(&self, keys_to_copy: &BTreeSet<&str>) -> eyre::Result<Self> {
Ok(Keyring {
r_config: if keys_to_copy.contains("r_config") {
Some(
self.r_config
.as_ref()
.context("no r_config in original keyring")?
.clone(),
)
} else {
None
},
w_config: if keys_to_copy.contains("w_config") {
Some(
self.w_config
.as_ref()
.context("no w_config in original keyring")?
.clone(),
)
} else {
None
},
r_bloblog_footer: if keys_to_copy.contains("r_bloblog_footer") {
Some(
self.r_bloblog_footer
.as_ref()
.context("no r_bloblog_footer in original keyring")?
.clone(),
)
} else {
None
},
w_bloblog_footer: if keys_to_copy.contains("w_bloblog_footer") {
Some(
self.w_bloblog_footer
.as_ref()
.context("no w_bloblog_footer in original keyring")?
.clone(),
)
} else {
None
},
r_bloblog_contents: if keys_to_copy.contains("r_bloblog_contents") {
Some(
self.r_bloblog_contents
.as_ref()
.context("no r_bloblog_contents in original keyring")?
.clone(),
)
} else {
None
},
w_bloblog_contents: if keys_to_copy.contains("w_bloblog_contents") {
Some(
self.w_bloblog_contents
.as_ref()
.context("no w_bloblog_contents in original keyring")?
.clone(),
)
} else {
None
},
r_locks: if keys_to_copy.contains("r_locks") {
Some(
self.r_locks
.as_ref()
.context("no r_locks in original keyring")?
.clone(),
)
} else {
None
},
w_locks: if keys_to_copy.contains("w_locks") {
Some(
self.w_locks
.as_ref()
.context("no w_locks in original keyring")?
.clone(),
)
} else {
None
},
r_pointer: if keys_to_copy.contains("r_pointer") {
Some(
self.r_pointer
.as_ref()
.context("no r_pointer in original keyring")?
.clone(),
)
} else {
None
},
w_pointer: if keys_to_copy.contains("w_pointer") {
Some(
self.w_pointer
.as_ref()
.context("no w_pointer in original keyring")?
.clone(),
)
} else {
None
},
})
}
}
pub fn generate_r_w_keys() -> (ReaderKey, WriterKey) {
let (encrypt, decrypt) = generate_asym_keypair();
let (sign, verify) = asym_signing_keypair();
@ -31,14 +195,14 @@ pub fn generate_r_w_keys() -> (ReaderKey, WriterKey) {
)
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct WriterKey {
// boxed because these take up a lot of stack space otherwise!
#[serde(flatten)]
inner: Box<WriterKeyInner>,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, PartialEq)]
struct WriterKeyInner {
encrypt: EncryptingKey,
sign: SigningKey,
@ -56,14 +220,14 @@ impl WriterKey {
}
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct ReaderKey {
// boxed because these take up a lot of stack space otherwise!
#[serde(flatten)]
inner: Box<ReaderKeyInner>,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct ReaderKeyInner {
decrypt: DecryptingKey,
verify: VerifyingKey,
@ -80,3 +244,20 @@ impl ReaderKey {
asymbox.unlock(&self.inner.decrypt, &self.inner.verify)
}
}
#[cfg(test)]
mod tests {
use crate::keyring::{Keyring, INDIVIDUAL_KEYS};
use std::collections::BTreeSet;
#[test]
fn test_keyring_partial_copy_full() {
let kr = Keyring::generate_new_master();
let keys_to_copy: BTreeSet<&str> = INDIVIDUAL_KEYS.into_iter().cloned().collect();
assert!(
kr.partial_copy(&keys_to_copy).unwrap() == kr,
"keyrings not identical!"
);
}
}