use super::super::peer::{PeerAuthority, open, request_parts, seal};
use super::*;
pub struct Device<'a> {
pub(super) device: Hash,
pub(super) signing: &'a Secret,
pub(super) recipient: &'a Secret,
bearer: &'a Secret,
}
impl<'a> Device<'a> {
pub fn seed(seed: &'a SeedAuthority) -> Self {
Self {
device: seed.genesis().device_id(),
signing: seed.protected_signing_seed(),
recipient: seed.protected_recipient_key(),
bearer: seed.bearer(),
}
}
pub(super) fn active<'m>(&self, membership: &'m Membership) -> Result<&'m Member> {
let member = membership.member(&self.device)?;
let private = HpkePrivate::from_bytes(self.recipient.expose())
.map_err(|_| anyhow::anyhow!("error membership-recipient"))?;
check(
member.sign
== SigningKey::from_bytes(self.signing.expose())
.verifying_key()
.to_bytes()
&& member.hpke.as_slice()
== <Kem as hpke::Kem>::sk_to_pk(&private).to_bytes().as_slice()
&& bool::from(member.verifier.ct_eq(&credential_verifier(
membership.genesis.context().vault_id,
self.device,
self.bearer,
))),
)?;
Ok(member)
}
pub fn validate(&self, membership: &Membership) -> Result<()> {
self.active(membership).map(|_| ())
}
pub fn prepare_invitation(
&self,
membership: &Membership,
expiry: u64,
) -> Result<(Invitation, Declaration)> {
self.invitation_with_psk(membership, expiry, Secret::generate()?)
}
pub(super) fn invitation_with_psk(
&self,
membership: &Membership,
expiry: u64,
psk: Secret,
) -> Result<(Invitation, Declaration)> {
let member = self.active(membership)?;
check(membership.device_count() < MAX_DEVICES)?;
membership.ensure_change_capacity()?;
let inv = Invitation {
vault: membership.genesis.context().vault_id,
inviter: member.hpke,
psk,
};
let mut body = b"AVID\0\x02\x01".to_vec();
for field in [
inv.vault,
membership.genesis.commitment(),
membership.head(),
self.device,
inv.inviter,
inv.handle(),
] {
body.extend(field);
}
body.extend(expiry.to_be_bytes());
let sig = SigningKey::from_bytes(self.signing.expose())
.sign(&cce("aven-e2ee/v1/pairing/declaration", &[&body]));
let mut raw = vec![1];
bytes(&mut raw, &body);
bytes(&mut raw, &sig.to_bytes());
Ok((inv, Declaration::from_record(membership, &raw)?))
}
pub fn prepare_admission(
&self,
membership: &Membership,
declaration: &Declaration,
invitation: &Invitation,
request: &[u8],
keys: &VerifiedKeys,
) -> Result<Vec<u8>> {
self.active(membership)?;
let d = Declaration::from_record(membership, declaration.record())?;
check(
d.inviter == self.device
&& invitation.vault == membership.genesis.context().vault_id
&& invitation.inviter == d.hpke
&& invitation.handle() == d.handle,
)?;
keys.validate(membership)?;
let handle = invitation.handle();
let (enc, cipher) = request_parts(request, &handle)?;
let plain = open(
invitation,
self.recipient,
&cce(
"aven-e2ee/v1/pairing/request",
&[&invitation.vault, &handle],
),
&cce(
"aven-e2ee/v1/pairing/request-aad",
&[&invitation.vault, &handle, &invitation.inviter],
),
enc,
cipher,
)?;
let recipient = Recipient::parse(&plain)?;
recipient.verify(&invitation.vault, &handle, &invitation.inviter)?;
membership.unique(&recipient, &handle)?;
let (state, core) = admission::state_core(membership, &d, request, &recipient);
let plain = admission::grant_plaintext(membership, &d, request, &recipient, keys);
let info = cce(
"aven-e2ee/v1/pairing/grant",
&[&invitation.vault, &handle, &hash(request)],
);
let (enc, cipher) = seal(invitation, &recipient.hpke, &info, &core, &plain)?;
let mut attachments = encoding::packages(1);
encoding::package(
&mut attachments,
1,
&recipient.device,
&recipient.hpke,
&enc,
&cipher,
);
let raw = admission::signed(self.signing, &core, &state, &attachments);
membership.append(d.record(), request, &raw)?;
Ok(raw)
}
}
#[derive(Clone)]
pub struct Declaration {
record: Vec<u8>,
pub(super) handle: Hash,
pub(super) inviter: Hash,
pub(super) hpke: Hash,
expiry: u64,
}
impl Declaration {
pub fn from_record(membership: &Membership, raw: &[u8]) -> Result<Self> {
check(raw.len() == DECLARATION_BYTES)?;
let mut r = Reader(raw);
check(r.take(1)? == [1])?;
let body = r.blob(207)?;
let signature = r.blob(64)?;
r.end()?;
let mut b = Reader(body);
check(b.take(7)? == b"AVID\0\x02\x01")?;
check(
b.array::<32>()? == membership.genesis.context().vault_id
&& b.array::<32>()? == membership.genesis.commitment(),
)?;
let anchor = b.array::<32>()?;
let anchor_sequence = membership
.heads
.iter()
.position(|head| *head == anchor)
.context("error membership-anchor")? as u64
+ 1;
let inviter = b.array()?;
let hpke = b.array()?;
let member = membership.member(&inviter)?;
check(member.admitted_at <= anchor_sequence && member.hpke == hpke)?;
let handle = b.array()?;
let expiry = u64::from_be_bytes(b.array()?);
b.end()?;
verify(
&member.sign,
"aven-e2ee/v1/pairing/declaration",
&[body],
signature,
)?;
Ok(Self {
record: raw.to_vec(),
handle,
inviter,
hpke,
expiry,
})
}
pub fn record(&self) -> &[u8] {
&self.record
}
pub fn commitment(&self) -> Hash {
hash(&self.record)
}
pub fn handle(&self) -> Hash {
self.handle
}
pub fn inviter(&self) -> Hash {
self.inviter
}
pub fn expiry(&self) -> u64 {
self.expiry
}
}
const ATTEMPT_KEY_BYTES: usize = 128;
pub struct Joiner(pub(super) PeerAuthority);
impl Joiner {
pub fn generate(invitation: Invitation) -> Result<Self> {
Ok(Self(PeerAuthority::generate(invitation)?))
}
pub fn protected_storage_bytes(&self) -> Zeroizing<Vec<u8>> {
self.0.protected_storage_bytes()
}
pub fn from_protected_storage(bytes: &[u8]) -> Result<Self> {
Ok(Self(PeerAuthority::from_protected_storage(bytes)?))
}
pub fn matches_invitation(&self, invitation: &Invitation) -> bool {
self.0.matches_invitation(invitation)
}
pub fn retry(&self, invitation: Invitation) -> Result<Self> {
self.same_context(&invitation)?;
ensure!(
!self.matches_invitation(&invitation),
"error enrollment-retry-context"
);
Ok(Self(self.0.retry(invitation)?))
}
pub fn attempt_bytes(&self) -> Zeroizing<Vec<u8>> {
Zeroizing::new(self.protected_storage_bytes()[ATTEMPT_KEY_BYTES..].to_vec())
}
pub fn attempt(&self, bytes: &[u8]) -> Result<Self> {
let mut raw = Zeroizing::new(self.protected_storage_bytes()[..ATTEMPT_KEY_BYTES].to_vec());
raw.extend_from_slice(bytes);
let attempt = Self::from_protected_storage(&raw)?;
self.same_context(&attempt.0.invitation)?;
Ok(attempt)
}
fn same_context(&self, invitation: &Invitation) -> Result<()> {
ensure!(
invitation.vault == self.0.invitation.vault
&& invitation.inviter == self.0.invitation.inviter,
"error enrollment-retry-context"
);
Ok(())
}
pub fn vault(&self) -> Hash {
self.0.vault()
}
pub fn handle(&self) -> Hash {
self.0.handle()
}
pub fn device(&self) -> Hash {
self.0.device()
}
pub fn bearer(&self) -> &Secret {
self.0.bearer()
}
pub fn request(&self) -> &[u8] {
self.0.request()
}
pub fn authority(&self) -> Device<'_> {
Device {
device: self.0.device,
signing: &self.0.signing,
recipient: &self.0.recipient,
bearer: &self.0.bearer,
}
}
pub fn open_provisional(&self, declaration: &[u8], record: &[u8]) -> Result<ProvisionalGrant> {
check(declaration.len() == DECLARATION_BYTES && record.len() <= MAX_RECORD_BYTES)?;
let (core, _, attachments, _) = encoding::components(record)?;
let recipient = self.0.recipient()?;
let mut r = Reader(attachments);
check(r.take(8)? == b"AVGA\0\x05\0\x01" && r.take(2)? == [1, 1])?;
check(r.blob(32)? == recipient.device && r.blob(32)? == recipient.hpke)?;
let enc = r.blob(32)?;
let cipher = encoding::blob(&mut r, MAX_KEY_PLAINTEXT_BYTES + 16)?;
r.end()?;
let info = cce(
"aven-e2ee/v1/pairing/grant",
&[&self.0.vault(), &self.0.handle(), &hash(self.request())],
);
let plain = open(
&self.0.invitation,
&self.0.recipient,
&info,
core,
enc,
cipher,
)?;
check(plain.len() >= GRANT_PREFIX_BYTES + 2 && plain.len() <= MAX_KEY_PLAINTEXT_BYTES)?;
let mut r = Reader(&plain);
check(
r.take(1)? == [3]
&& r.array::<32>()? == self.0.vault()
&& r.array::<32>()? == self.0.handle()
&& r.array::<32>()? == hash(declaration)
&& r.array::<32>()? == hash(self.request())
&& r.array::<32>()? == self.device(),
)?;
let genesis = r.array()?;
let publication = r.array()?;
let predecessor = r.array()?;
r.take(64)?;
let descriptor = r.array()?;
Ok(ProvisionalGrant {
plaintext: plain,
genesis,
publication,
predecessor,
descriptor,
outcome: hash(record),
})
}
pub fn verify_enrollment(
&self,
predecessor: &Membership,
declaration: &[u8],
record: &[u8],
) -> Result<VerifiedEnrollment> {
let grant = self.open_provisional(declaration, record)?;
let d = Declaration::from_record(predecessor, declaration)?;
let membership = predecessor.admit(&d, self.request(), record)?;
check(d.hpke == self.0.invitation.inviter)?;
let recipient = self.0.recipient()?;
let member = membership.member(&self.device())?;
check(
member.sign == recipient.sign
&& member.hpke == recipient.hpke
&& member.verifier == recipient.verifier
&& member.admission == d.handle,
)?;
let mut r = Reader(&grant.plaintext[GRANT_PREFIX_BYTES..]);
let keys = VerifiedKeys::read(predecessor, &mut r)?;
let expected =
admission::grant_plaintext(predecessor, &d, self.request(), &recipient, &keys);
check(bool::from(
grant.plaintext.as_slice().ct_eq(expected.as_slice()),
))?;
Ok(VerifiedEnrollment { membership, keys })
}
}
pub struct ProvisionalGrant {
plaintext: Zeroizing<Vec<u8>>,
pub genesis: Hash,
pub publication: Hash,
pub predecessor: Hash,
pub descriptor: Hash,
pub outcome: Hash,
}
pub struct VerifiedEnrollment {
membership: Membership,
keys: VerifiedKeys,
}
impl VerifiedEnrollment {
pub fn genesis(&self) -> &Genesis {
self.membership.genesis()
}
pub fn publication(&self) -> &Publication {
self.membership.publication()
}
pub fn checkpoint(&self) -> Hash {
self.membership.head()
}
pub fn membership(&self) -> &Membership {
&self.membership
}
pub fn keys(&self) -> &VerifiedKeys {
&self.keys
}
pub fn key(&self) -> &LocalSharedStatePackageKey {
self.keys
.key(self.membership.genesis.context().generation_id)
.expect("verified bootstrap coverage")
}
}