|
|
@@ -0,0 +1,192 @@
|
|
|
+/*! A module for the protocol for the user to get promoted from
|
|
|
+untrusted (trust level 0) to trusted (trust level 1).
|
|
|
+
|
|
|
+They are allowed to do this as long as UNTRUSTED_INTERVAL days have
|
|
|
+passed since they obtained their level 0 Lox credential, and their
|
|
|
+bridge (level 0 users get put in a one-bridge bucket) has not been
|
|
|
+blocked. (Blocked bridges in one-bridge buckets will have their entries
|
|
|
+removed from the bridge authority's trust_promotion table.)
|
|
|
+
|
|
|
+The user presents their current Lox credential:
|
|
|
+- id: revealed
|
|
|
+- bucket: blinded
|
|
|
+- trust_level: revealed to be 0
|
|
|
+- level_since: blinded, but proved in ZK that it's at least
|
|
|
+ UNTRUSTED_INTERVAL days ago
|
|
|
+- invites_remaining: revealed to be 0
|
|
|
+- blockages: revealed to be 0
|
|
|
+
|
|
|
+They will receive in return the encrypted MAC (Pk, EncQk) for their
|
|
|
+implicit Migration Key credential with attributes id and bucket,
|
|
|
+along with a HashMap of encrypted Migration credentials. For each
|
|
|
+(from_i, to_i) in the BA's migration list, there will be an entry in
|
|
|
+the HashMap with key H1(id, from_attr_i, Qk_i) and value
|
|
|
+Enc_{H2(id, from_attr_i, Qk_i)}(to_attr_i, P_i, Q_i). Here H1 and H2
|
|
|
+are the first 16 bytes and the second 16 bytes respectively of the
|
|
|
+SHA256 hash of the input, P_i and Q_i are a MAC on the Migration
|
|
|
+credential with attributes id, from_attr_i, and to_attr_i. Qk_i is the
|
|
|
+value EncQk would decrypt to if bucket were equal to from_attr_i. */
|
|
|
+
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use super::super::dup_filter::SeenType;
|
|
|
+use super::super::scalar_u32;
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use super::super::BridgeAuth;
|
|
|
+use super::errors::CredentialError;
|
|
|
+use crate::lox_creds::{Lox, Migration, MigrationKey};
|
|
|
+use crate::migration_table;
|
|
|
+use crate::migration_table::ENC_MIGRATION_BYTES;
|
|
|
+use cmz::*;
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use curve25519_dalek::ristretto::RistrettoBasepointTable;
|
|
|
+use curve25519_dalek::ristretto::RistrettoPoint as G;
|
|
|
+use group::Group;
|
|
|
+use rand_core::RngCore;
|
|
|
+use sha2::Sha512;
|
|
|
+use std::collections::HashMap;
|
|
|
+
|
|
|
+/// The minimum number of days a user has to be at trust level 0
|
|
|
+/// (untrusted) with their (single) bridge unblocked before they can
|
|
|
+/// move to level 1.
|
|
|
+///
|
|
|
+/// The implementation also puts an upper bound of UNTRUSTED_INTERVAL +
|
|
|
+/// 511 days, which is not unreasonable; we want users to be engaging
|
|
|
+/// with the system in order to move up trust levels.
|
|
|
+pub const UNTRUSTED_INTERVAL: u32 = 30;
|
|
|
+
|
|
|
+muCMZProtocol! { trust_promotion<credential_expiry, eligibility_max_age>,
|
|
|
+ L: Lox { id: R, bucket: H, trust_level: R, level_since: H, invites_remaining: R, blockages: R },
|
|
|
+ M: MigrationKey { lox_id: J, from_bucket: H} ,
|
|
|
+ L.bucket = M.from_bucket,
|
|
|
+ // credential_expiry <= L.level_since,
|
|
|
+ // L.level_since <= eligibility_max_age,
|
|
|
+}
|
|
|
+
|
|
|
+pub fn request(
|
|
|
+ L: Lox,
|
|
|
+ mig_pubkeys: CMZPubkey<G>,
|
|
|
+ today: u32,
|
|
|
+) -> Result<(trust_promotion::Request, trust_promotion::ClientState), CredentialError> {
|
|
|
+ let mut rng = rand::thread_rng();
|
|
|
+ cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
|
|
|
+
|
|
|
+ // Ensure that the credenials can be correctly shown; that is, the
|
|
|
+ // ids match and the Lox credential bucket matches the Migration
|
|
|
+ // credential from_bucket
|
|
|
+ if L.id.is_none() {
|
|
|
+ return Err(CredentialError::CredentialMismatch);
|
|
|
+ }
|
|
|
+
|
|
|
+ // This protocol only allows migrating from trust level 0 to trust
|
|
|
+ // level 1
|
|
|
+ if let Some(ls) = L.level_since {
|
|
|
+ let level_since = match scalar_u32(&ls) {
|
|
|
+ Some(v) => v,
|
|
|
+ None => {
|
|
|
+ return Err(CredentialError::InvalidField(
|
|
|
+ String::from("level_since"),
|
|
|
+ String::from("could not be converted to u32"),
|
|
|
+ ))
|
|
|
+ }
|
|
|
+ };
|
|
|
+ if level_since + UNTRUSTED_INTERVAL > today {
|
|
|
+ return Err(CredentialError::TimeThresholdNotMet(
|
|
|
+ level_since + UNTRUSTED_INTERVAL - today,
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ let diffdays = today - (level_since + UNTRUSTED_INTERVAL);
|
|
|
+ if diffdays > 511 {
|
|
|
+ return Err(CredentialError::CredentialExpired);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let eligibility_max_age = today - UNTRUSTED_INTERVAL;
|
|
|
+
|
|
|
+ let M = MigrationKey::using_pubkey(&mig_pubkeys);
|
|
|
+ let params = trust_promotion::Params {
|
|
|
+ credential_expiry: (eligibility_max_age - 511).into(),
|
|
|
+ eligibility_max_age: eligibility_max_age.into(),
|
|
|
+ };
|
|
|
+
|
|
|
+ match trust_promotion::prepare(&mut rng, &L, M, ¶ms) {
|
|
|
+ Ok(req_state) => Ok(req_state),
|
|
|
+ Err(e) => Err(CredentialError::CMZError(e)),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+impl BridgeAuth {
|
|
|
+ pub fn handle_trust_promotion(
|
|
|
+ &mut self,
|
|
|
+ req: trust_promotion::Request,
|
|
|
+ ) -> Result<
|
|
|
+ (
|
|
|
+ trust_promotion::Reply,
|
|
|
+ HashMap<[u8; 16], [u8; ENC_MIGRATION_BYTES]>,
|
|
|
+ ),
|
|
|
+ CredentialError,
|
|
|
+ > {
|
|
|
+ let mut rng = rand::thread_rng();
|
|
|
+ let reqbytes = req.as_bytes();
|
|
|
+ let recvreq = trust_promotion::Request::try_from(&reqbytes[..]).unwrap();
|
|
|
+
|
|
|
+ let today = self.today();
|
|
|
+ match trust_promotion::handle(
|
|
|
+ &mut rng,
|
|
|
+ recvreq,
|
|
|
+ |L: &mut Lox, M: &mut MigrationKey| {
|
|
|
+ L.set_privkey(&self.lox_priv);
|
|
|
+ M.set_privkey(&self.migrationkey_priv);
|
|
|
+ let eligibility_max_age = today - UNTRUSTED_INTERVAL;
|
|
|
+ Ok(trust_promotion::Params {
|
|
|
+ credential_expiry: (eligibility_max_age - 511).into(),
|
|
|
+ eligibility_max_age: eligibility_max_age.into(),
|
|
|
+ })
|
|
|
+ },
|
|
|
+ |L: &Lox, _M: &MigrationKey| {
|
|
|
+ if self.id_filter.filter(&L.id.unwrap()) == SeenType::Seen
|
|
|
+ || self.trust_promotion_filter.filter(&L.id.unwrap()) == SeenType::Seen
|
|
|
+ {
|
|
|
+ return Err(CMZError::RevealAttrMissing("id", "Credential Expired"));
|
|
|
+ }
|
|
|
+ Ok(())
|
|
|
+ },
|
|
|
+ ) {
|
|
|
+ Ok((response, (L_issuer, M_issuer))) => {
|
|
|
+ let Pktable: RistrettoBasepointTable =
|
|
|
+ RistrettoBasepointTable::create(&M_issuer.MAC.P);
|
|
|
+ let enc_migration_table = self.trustup_migration_table.encrypt_table(
|
|
|
+ L_issuer.id.unwrap(),
|
|
|
+ &self.bridge_table,
|
|
|
+ &Pktable,
|
|
|
+ &self.migration_priv,
|
|
|
+ &self.migrationkey_priv,
|
|
|
+ );
|
|
|
+ Ok((response, enc_migration_table))
|
|
|
+ }
|
|
|
+ Err(e) => Err(CredentialError::CMZError(e)),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub fn handle_response(
|
|
|
+ migration_pubkey: CMZPubkey<G>,
|
|
|
+ state: trust_promotion::ClientState,
|
|
|
+ rep: trust_promotion::Reply,
|
|
|
+ enc_migration_table: HashMap<[u8; 16], [u8; ENC_MIGRATION_BYTES]>,
|
|
|
+) -> Result<Migration, CMZError> {
|
|
|
+ let replybytes = rep.as_bytes();
|
|
|
+ let recvreply = trust_promotion::Reply::try_from(&replybytes[..]).unwrap();
|
|
|
+ let migkey = match state.finalize(recvreply) {
|
|
|
+ Ok(cred) => cred,
|
|
|
+ Err(_e) => return Err(CMZError::Unknown),
|
|
|
+ };
|
|
|
+ match migration_table::decrypt_cred(
|
|
|
+ migkey,
|
|
|
+ migration_table::MigrationType::Blockage,
|
|
|
+ migration_pubkey,
|
|
|
+ &enc_migration_table,
|
|
|
+ ) {
|
|
|
+ Some(cred) => Ok(cred),
|
|
|
+ None => Err(CMZError::Unknown),
|
|
|
+ }
|
|
|
+}
|