#[cfg(feature = "bridgeauth")]
use super::super::dup_filter::SeenType;
#[cfg(feature = "bridgeauth")]
use super::super::BridgeAuth;
use super::super::{scalar_u32, G};
use super::errors::CredentialError;
use crate::lox_creds::{BucketReachability, Lox};
use cmz::*;
use group::Group;
use rand::{CryptoRng, RngCore};
use sha2::Sha512;
const SESSION_ID: &[u8] = b"level_up";
pub const MAX_LEVEL: usize = 4;
pub const LEVEL_INTERVAL: [u32; MAX_LEVEL + 1] = [0, 14, 28, 56, 84];
pub const LEVEL_INVITATIONS: [u32; MAX_LEVEL + 1] = [0, 2, 4, 6, 8];
pub const MAX_BLOCKAGES: [u32; MAX_LEVEL + 1] = [0, 4, 3, 2, 2];
muCMZProtocol! { level_up<credential_expiry, eligibility_max_age, max_blockage, today>,
[ L: Lox { id: R, bucket: H, trust_level: R, level_since: H, invites_remaining: H, blockages: H },
B: BucketReachability { date: R, bucket: H } ],
N: Lox {id: J, bucket: H, trust_level: R, level_since: S, invites_remaining: I, blockages: H },
(credential_expiry..=eligibility_max_age).contains(L.level_since),
(0..=max_blockage).contains(L.blockages),
B.date = today,
B.bucket = L.bucket,
N.bucket = L.bucket,
N.trust_level = L.trust_level + 1,
N.blockages = L.blockages,
}
pub fn request(
rng: &mut (impl CryptoRng + RngCore),
L: Lox,
B: BucketReachability,
today: u32,
) -> Result<(level_up::Request, level_up::ClientState), CredentialError> {
cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
let level_since: u32 = match scalar_u32(&L.level_since.unwrap()) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("level_since"),
String::from("could not be converted to u32"),
))
}
};
let trust_level: u32 = match scalar_u32(&L.trust_level.unwrap()) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
String::from("could not be converted to u32"),
))
}
};
if trust_level < 1 || (trust_level as usize) > MAX_LEVEL {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
format!("level {:?} not in range", trust_level),
));
}
let level_interval: u32 = match LEVEL_INTERVAL.get(trust_level as usize) {
Some(&v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("trust_level"),
format!("level {:?} not in range", trust_level),
))
}
};
if level_since + level_interval > today {
return Err(CredentialError::TimeThresholdNotMet(
level_since + level_interval - today,
));
}
let diffdays = today - (level_since + level_interval);
if diffdays > 511 {
return Err(CredentialError::CredentialExpired);
}
let blockages = match scalar_u32(&L.blockages.unwrap()) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("blockages"),
String::from("could not be converted to u32"),
))
}
};
if blockages > MAX_BLOCKAGES[trust_level as usize] {
return Err(CredentialError::ExceededBlockagesThreshold);
}
if L.bucket.is_some_and(|b| b != B.bucket.unwrap()) {
return Err(CredentialError::CredentialMismatch);
}
let reach_date: u32 = match scalar_u32(&B.date.unwrap()) {
Some(v) => v,
None => {
return Err(CredentialError::InvalidField(
String::from("date"),
String::from("could not be converted to u32"),
))
}
};
if reach_date != today {
return Err(CredentialError::InvalidField(
String::from("date"),
String::from("reachability credential must be generated today"),
));
}
let new_level = if (trust_level as usize) < MAX_LEVEL {
trust_level + 1
} else {
trust_level
};
let mut N = Lox::using_pubkey(L.get_pubkey());
N.bucket = L.bucket;
N.trust_level = Some(new_level.into());
N.invites_remaining = Some(LEVEL_INVITATIONS[trust_level as usize].into());
N.blockages = L.blockages;
let eligibility_max_age = today - (LEVEL_INTERVAL[trust_level as usize]);
let params = level_up::Params {
credential_expiry: (eligibility_max_age - 511).into(),
eligibility_max_age: eligibility_max_age.into(),
max_blockage: MAX_BLOCKAGES[new_level as usize].into(),
today: today.into(),
};
match level_up::prepare(rng, SESSION_ID, &L, &B, N, ¶ms) {
Ok(req_state) => Ok(req_state),
Err(e) => Err(CredentialError::CMZError(e)),
}
}
#[cfg(feature = "bridgeauth")]
impl BridgeAuth {
pub fn handle_level_up(
&mut self,
req: level_up::Request,
) -> Result<level_up::Reply, CredentialError> {
let mut rng = rand::thread_rng();
let reqbytes = req.as_bytes();
let recvreq = level_up::Request::try_from(&reqbytes[..]).unwrap();
let today = self.today();
match level_up::handle(
&mut rng,
SESSION_ID,
recvreq,
|L: &mut Lox, B: &mut BucketReachability, N: &mut Lox| {
let trust_level: u32 = match scalar_u32(&L.trust_level.unwrap()) {
Some(v) if v as usize >= 1 && v as usize <= MAX_LEVEL => v,
_ => {
return Err(CMZError::RevealAttrMissing(
"trust_level",
"Could not be converted to u32 or value not in range",
));
}
};
let eligibility_max_age: u32 = today - LEVEL_INTERVAL[trust_level as usize];
L.set_privkey(&self.lox_priv);
B.set_privkey(&self.reachability_priv);
N.set_privkey(&self.lox_priv);
N.trust_level = Some((trust_level + 1).into());
N.level_since = Some(today.into());
N.invites_remaining = Some(LEVEL_INVITATIONS[trust_level as usize].into());
Ok(level_up::Params {
credential_expiry: (eligibility_max_age - 511).into(),
eligibility_max_age: eligibility_max_age.into(),
max_blockage: MAX_BLOCKAGES[(trust_level + 1) as usize].into(),
today: today.into(),
})
},
|L: &Lox, _B: &BucketReachability, _N: &Lox| {
if self.id_filter.filter(&L.id.unwrap()) == SeenType::Seen {
return Err(CMZError::RevealAttrMissing("id", ""));
}
Ok(())
},
) {
Ok((response, (_L_issuer, _B_isser, _N_issuer))) => Ok(response),
Err(e) => Err(CredentialError::CMZError(e)),
}
}
}
pub fn handle_response(
state: level_up::ClientState,
rep: level_up::Reply,
) -> Result<Lox, CMZError> {
let replybytes = rep.as_bytes();
let recvreply = level_up::Reply::try_from(&replybytes[..]).unwrap();
match state.finalize(recvreply) {
Ok(cred) => Ok(cred),
Err(_e) => Err(CMZError::Unknown),
}
}
#[cfg(all(test, feature = "bridgeauth"))]
mod tests {
use crate::mock_auth::TestHarness;
#[test]
fn test_level_up() {
let mut th = TestHarness::new();
let rng = &mut rand::thread_rng();
let invite = th.bdb.invite().unwrap();
let mut lox_cred = th.open_invite(rng, &invite);
let mig_cred = th.trust_promotion(rng, lox_cred.clone());
lox_cred = th.migration(rng, lox_cred.clone(), mig_cred.clone());
lox_cred = th.level_up(rng, lox_cred.clone());
th.verify_lox(&lox_cred);
}
}