Add `yama keyring create` command
This commit is contained in:
parent
53886aad46
commit
1ac9bb6d8d
|
@ -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(())
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 } => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"] }
|
||||
|
||||
|
|
|
@ -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!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue