use zeroize::Zeroizing;
use crate::crypto::unwrap_org_key;
use crate::org_key_directory::{OrgKeyDirectory, OrgKeyDirectoryError};
use ed25519_dalek::VerifyingKey;
use x25519_dalek::StaticSecret;
pub struct ResolvedOrgRoot {
pub root: Zeroizing<[u8; 32]>,
pub epoch: u64,
}
pub fn resolve_org_root(
directory: &dyn OrgKeyDirectory,
org: &str,
my_secret: &StaticSecret,
my_user_id: &str,
trusted: &[VerifyingKey],
) -> Result<Option<ResolvedOrgRoot>, OrgKeyDirectoryError> {
let candidates = directory.fetch_wrapped_for(my_user_id)?;
for w in candidates {
if w.org != org {
continue;
}
if let Ok(k) = unwrap_org_key(&w, my_secret, my_user_id, trusted) {
return Ok(Some(ResolvedOrgRoot {
root: Zeroizing::new(k),
epoch: w.epoch,
}));
}
}
Ok(None)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::{ed25519_verifying, wrap_org_key, x25519_public, StretchedMaster};
use crate::org_key_directory::{InMemoryOrgKeyDirectory, OrgKeyDirectory};
fn x25519_id(secret: &[u8], user: &str) -> StaticSecret {
crate::crypto::derive_x25519_identity(
&StretchedMaster::from_issued_high_entropy(secret, user),
user,
)
}
fn ed25519_id(secret: &[u8], user: &str) -> ed25519_dalek::SigningKey {
crate::crypto::derive_ed25519_identity(
&StretchedMaster::from_issued_high_entropy(secret, user),
user,
)
}
fn granter() -> ed25519_dalek::SigningKey {
ed25519_id(b"granter-login", "acc_granter")
}
#[allow(clippy::too_many_arguments)]
fn publish(
dir: &mut InMemoryOrgKeyDirectory,
k_org: &[u8; 32],
org: &str,
epoch: u64,
recipient: &str,
recipient_pub: &x25519_dalek::PublicKey,
publisher: &str,
signer: &ed25519_dalek::SigningKey,
) {
let w = wrap_org_key(
k_org,
org,
epoch,
recipient,
recipient_pub,
publisher,
signer,
)
.unwrap();
dir.publish_wrapped(&w).unwrap();
}
#[test]
fn resolves_the_root_for_a_granted_member() {
let mut dir = InMemoryOrgKeyDirectory::new();
let k_org = [7u8; 32];
let alice = x25519_id(b"alice", "acc_alice");
publish(
&mut dir,
&k_org,
"acme",
1,
"acc_alice",
&x25519_public(&alice),
"acc_granter",
&granter(),
);
let trusted = [ed25519_verifying(&granter())];
let got = resolve_org_root(&dir, "acme", &alice, "acc_alice", &trusted)
.unwrap()
.expect("granted member resolves");
assert_eq!(*got.root, k_org);
assert_eq!(got.epoch, 1);
}
#[test]
fn ungranted_member_is_ok_none_not_err() {
let dir = InMemoryOrgKeyDirectory::new(); let alice = x25519_id(b"alice", "acc_alice");
let trusted = [ed25519_verifying(&granter())];
assert!(
resolve_org_root(&dir, "acme", &alice, "acc_alice", &trusted)
.unwrap()
.is_none()
);
}
#[test]
fn wrap_from_untrusted_publisher_is_not_resolved() {
let mut dir = InMemoryOrgKeyDirectory::new();
let k_org = [9u8; 32];
let alice = x25519_id(b"alice", "acc_alice");
let mallory = ed25519_id(b"mallory", "acc_mallory");
publish(
&mut dir,
&k_org,
"acme",
1,
"acc_alice",
&x25519_public(&alice),
"acc_mallory",
&mallory,
);
let trusted = [ed25519_verifying(&granter())];
assert!(
resolve_org_root(&dir, "acme", &alice, "acc_alice", &trusted)
.unwrap()
.is_none()
);
}
#[test]
fn newest_grantable_epoch_wins() {
let mut dir = InMemoryOrgKeyDirectory::new();
let alice = x25519_id(b"alice", "acc_alice");
let trusted = [ed25519_verifying(&granter())];
publish(
&mut dir,
&[1u8; 32],
"acme",
1,
"acc_alice",
&x25519_public(&alice),
"acc_granter",
&granter(),
);
publish(
&mut dir,
&[2u8; 32],
"acme",
3,
"acc_alice",
&x25519_public(&alice),
"acc_granter",
&granter(),
);
let got = resolve_org_root(&dir, "acme", &alice, "acc_alice", &trusted)
.unwrap()
.unwrap();
assert_eq!(got.epoch, 3, "newest resolvable epoch selected");
assert_eq!(*got.root, [2u8; 32]);
}
#[test]
fn wrap_for_a_different_org_is_skipped() {
let mut dir = InMemoryOrgKeyDirectory::new();
let alice = x25519_id(b"alice", "acc_alice");
let trusted = [ed25519_verifying(&granter())];
publish(
&mut dir,
&[5u8; 32],
"other",
1,
"acc_alice",
&x25519_public(&alice),
"acc_granter",
&granter(),
);
assert!(
resolve_org_root(&dir, "acme", &alice, "acc_alice", &trusted)
.unwrap()
.is_none()
);
}
#[test]
fn directory_error_propagates_as_err_not_ok_none() {
struct FailingDir;
impl OrgKeyDirectory for FailingDir {
fn publish_wrapped(
&mut self,
_: &crate::crypto::WrappedOrgKey,
) -> Result<(), OrgKeyDirectoryError> {
unimplemented!()
}
fn fetch_wrapped(
&self,
_: u64,
_: &str,
) -> Result<Option<crate::crypto::WrappedOrgKey>, OrgKeyDirectoryError> {
unimplemented!()
}
fn fetch_wrapped_for(
&self,
_: &str,
) -> Result<Vec<crate::crypto::WrappedOrgKey>, OrgKeyDirectoryError> {
Err(OrgKeyDirectoryError::Io(std::io::Error::other(
"unreachable",
)))
}
fn publish_pubkey(&mut self, _: &str, _: &str) -> Result<(), OrgKeyDirectoryError> {
unimplemented!()
}
fn fetch_pubkeys(
&self,
) -> Result<Vec<crate::org_key_directory::MemberPublicKey>, OrgKeyDirectoryError>
{
unimplemented!()
}
}
let alice = x25519_id(b"alice", "acc_alice");
let trusted = [ed25519_verifying(&granter())];
assert!(resolve_org_root(&FailingDir, "acme", &alice, "acc_alice", &trusted).is_err());
}
}