|
|
@@ -0,0 +1,156 @@
|
|
|
+/*! A module for the protocol for the user to redeem an open invitation
|
|
|
+with the BA (bridge authority) to receive their initial Lox
|
|
|
+credential.
|
|
|
+
|
|
|
+The credential will have attributes:
|
|
|
+
|
|
|
+- id: jointly chosen by the user and BA
|
|
|
+- bucket: set by the BA
|
|
|
+- trust_level: 0
|
|
|
+- level_since: today
|
|
|
+- invites_remaining: 0
|
|
|
+- blockages: 0
|
|
|
+
|
|
|
+*/
|
|
|
+
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use super::super::bridge_table;
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use super::super::bridge_table::BridgeLine;
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use super::super::dup_filter::SeenType;
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use super::super::OPENINV_LENGTH;
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use super::super::{BridgeAuth, BridgeDb};
|
|
|
+use super::errors::CredentialError;
|
|
|
+use crate::lox_creds::Lox;
|
|
|
+use cmz::*;
|
|
|
+use curve25519_dalek::ristretto::RistrettoPoint as G;
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use curve25519_dalek::scalar::Scalar;
|
|
|
+use group::Group;
|
|
|
+use rand_core::RngCore;
|
|
|
+use sha2::Sha512;
|
|
|
+
|
|
|
+muCMZProtocol! { open_invitation,
|
|
|
+ ,
|
|
|
+ L: Lox {id: J, bucket: S, trust_level: I, level_since: S, invites_remaining: I, blockages: I },
|
|
|
+}
|
|
|
+
|
|
|
+/// Prepare the open invitation request to send to the Lox Authority
|
|
|
+/// Note that preparing the request does not require an open invitation, but an invitation
|
|
|
+/// must be sent along with the prepared open_inivtation::Request to the Lox authority
|
|
|
+pub fn request(
|
|
|
+ pubkeys: CMZPubkey<G>,
|
|
|
+) -> Result<(open_invitation::Request, open_invitation::ClientState), CredentialError> {
|
|
|
+ let mut rng = rand::thread_rng();
|
|
|
+ cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
|
|
|
+
|
|
|
+ let L = Lox::using_pubkey(&pubkeys);
|
|
|
+
|
|
|
+ match open_invitation::prepare(&mut rng, L) {
|
|
|
+ Ok(req_state) => Ok(req_state),
|
|
|
+ Err(e) => Err(CredentialError::CMZError(e)),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+impl BridgeAuth {
|
|
|
+ pub fn open_invitation(
|
|
|
+ &mut self,
|
|
|
+ req: open_invitation::Request,
|
|
|
+ invite: &[u8; OPENINV_LENGTH],
|
|
|
+ ) -> Result<(open_invitation::Reply, BridgeLine), CredentialError> {
|
|
|
+ // Check the signature on the open_invite, first with the old key, then with the new key.
|
|
|
+ // We manually match here because we're changing the Err type from SignatureError
|
|
|
+ // to ProofError
|
|
|
+ let mut old_token: Option<((Scalar, u32), usize)> = Default::default();
|
|
|
+ let invite_id: Scalar;
|
|
|
+ let bucket_id: u32;
|
|
|
+ // If there are old openinv keys, check them first
|
|
|
+ for (i, old_openinv_key) in self.old_keys.bridgedb_key.iter().enumerate() {
|
|
|
+ old_token = match BridgeDb::verify(*invite, *old_openinv_key) {
|
|
|
+ Ok(res) => Some((res, i)),
|
|
|
+ Err(_) => None,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if verifying with the old key succeeded, if it did, check if it has been seen
|
|
|
+ if old_token.is_some() {
|
|
|
+ // Only proceed if the invite_id is fresh
|
|
|
+ (invite_id, bucket_id) = old_token.unwrap().0;
|
|
|
+ if self
|
|
|
+ .old_filters
|
|
|
+ .openinv_filter
|
|
|
+ .get_mut(old_token.unwrap().1)
|
|
|
+ .unwrap()
|
|
|
+ .filter(&invite_id)
|
|
|
+ == SeenType::Seen
|
|
|
+ {
|
|
|
+ return Err(CredentialError::CredentialExpired);
|
|
|
+ }
|
|
|
+ // If it didn't, try verifying with the new key
|
|
|
+ } else {
|
|
|
+ (invite_id, bucket_id) = match BridgeDb::verify(*invite, self.bridgedb_pub) {
|
|
|
+ Ok(res) => res,
|
|
|
+ // Also verify that the request doesn't match with an old openinv_key
|
|
|
+ Err(_) => {
|
|
|
+ return Err(CredentialError::InvalidField(
|
|
|
+ "invitation".to_string(),
|
|
|
+ "pubkey".to_string(),
|
|
|
+ ))
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // Only proceed if the invite_id is fresh
|
|
|
+ if self.bridgedb_pub_filter.filter(&invite_id) == SeenType::Seen {
|
|
|
+ return Err(CredentialError::CredentialExpired);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // And also check that the bucket id is valid
|
|
|
+ if !self.bridge_table.buckets.contains_key(&bucket_id) {
|
|
|
+ return Err(CredentialError::InvalidField(
|
|
|
+ "invitation".to_string(),
|
|
|
+ "bucket".to_string(),
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ let mut rng = rand::thread_rng();
|
|
|
+ let reqbytes = req.as_bytes();
|
|
|
+ // Create the bucket attribute (Scalar), which is a combination
|
|
|
+ // of the bucket id (u32) and the bucket's decryption key ([u8; 16])
|
|
|
+ let bucket_key = self.bridge_table.keys.get(&bucket_id).unwrap();
|
|
|
+ let bucket: Scalar = bridge_table::to_scalar(bucket_id, bucket_key);
|
|
|
+ let bridge_lines = self.bridge_table.buckets.get(&bucket_id).unwrap();
|
|
|
+ let bridge_line = bridge_lines[0];
|
|
|
+
|
|
|
+ let recvreq = open_invitation::Request::try_from(&reqbytes[..]).unwrap();
|
|
|
+ match open_invitation::handle(
|
|
|
+ &mut rng,
|
|
|
+ recvreq,
|
|
|
+ |L: &mut Lox| {
|
|
|
+ L.set_privkey(&self.lox_priv);
|
|
|
+ L.bucket = Some(bucket);
|
|
|
+ L.level_since = Some(self.today().into());
|
|
|
+ Ok(())
|
|
|
+ },
|
|
|
+ |_L: &Lox| Ok(()),
|
|
|
+ ) {
|
|
|
+ Ok((response, _L_issuer)) => Ok((response, bridge_line)),
|
|
|
+ Err(e) => Err(CredentialError::CMZError(e)),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub fn handle_response(
|
|
|
+ state: open_invitation::ClientState,
|
|
|
+ rep: open_invitation::Reply,
|
|
|
+) -> Result<Lox, CMZError> {
|
|
|
+ let replybytes = rep.as_bytes();
|
|
|
+ let recvreply = open_invitation::Reply::try_from(&replybytes[..]).unwrap();
|
|
|
+ match state.finalize(recvreply) {
|
|
|
+ Ok(cred) => Ok(cred),
|
|
|
+ Err(_e) => Err(CMZError::Unknown),
|
|
|
+ }
|
|
|
+}
|