mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-06-13 10:42:12 +00:00
merge into one single crate
This commit is contained in:
parent
053fbeb66e
commit
37551cacce
38 changed files with 79 additions and 2034 deletions
38
rust/Cargo.lock
generated
38
rust/Cargo.lock
generated
|
|
@ -391,12 +391,6 @@ version = "0.9.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
|
|
@ -562,7 +556,7 @@ version = "0.7.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid 0.9.6",
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
|
@ -594,7 +588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.4",
|
||||
"const-oid 0.9.6",
|
||||
"const-oid",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
|
@ -606,7 +600,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.0",
|
||||
"const-oid 0.10.2",
|
||||
"crypto-common 0.2.1",
|
||||
"ctutils",
|
||||
]
|
||||
|
|
@ -1857,25 +1850,6 @@ dependencies = [
|
|||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protocols"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"blahaj",
|
||||
"hmac 0.13.0",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"rand 0.10.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.0",
|
||||
"sqlx",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
|
|
@ -1991,7 +1965,7 @@ version = "0.9.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
|
||||
dependencies = [
|
||||
"const-oid 0.9.6",
|
||||
"const-oid",
|
||||
"digest 0.10.7",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
|
|
@ -2012,20 +1986,24 @@ dependencies = [
|
|||
"aes-gcm",
|
||||
"android-native-keyring-store",
|
||||
"apple-native-keyring-store",
|
||||
"base64",
|
||||
"blahaj",
|
||||
"chrono",
|
||||
"flutter_rust_bridge",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac 0.13.0",
|
||||
"keyring-core",
|
||||
"libsqlite3-sys",
|
||||
"paste",
|
||||
"postcard",
|
||||
"pretty_env_logger",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"protocols",
|
||||
"rand 0.10.1",
|
||||
"scrypt",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
|
|
|
|||
|
|
@ -36,7 +36,11 @@ libsqlite3-sys = { version = "0.35.0", features = [
|
|||
tokio = { version = "1.44", features = ["full"] }
|
||||
tracing = "0.1.44"
|
||||
rand = "0.10.1"
|
||||
protocols = { path = "../rust_dependencies/protocols" }
|
||||
prost = "0.14.1"
|
||||
blahaj = "0.6.0"
|
||||
serde_json = "1.0"
|
||||
base64 = "0.22.1"
|
||||
hmac = "0.13.0"
|
||||
hkdf = "0.12.4"
|
||||
sha2 = "0.10.8"
|
||||
aes-gcm = "0.10.3"
|
||||
|
|
|
|||
5
rust/build.rs
Normal file
5
rust/build.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
use std::io::Result;
|
||||
fn main() -> Result<()> {
|
||||
prost_build::compile_protos(&["src/user_discovery/types.proto"], &["src/"])?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ impl BackupIdentity {
|
|||
|
||||
let key_manager: KeyManager = postcard::from_bytes(&decrypted_bytes)?;
|
||||
|
||||
key_manager.store_to_keychain(&secure_storage)?;
|
||||
key_manager.store_to_keychain(secure_storage)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ pub(crate) mod log;
|
|||
mod macros;
|
||||
pub(crate) mod user_discovery;
|
||||
|
||||
use crate::user_discovery::traits::{AnnouncedUser, OtherPromotion};
|
||||
use flutter_rust_bridge::DartFnFuture;
|
||||
use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion};
|
||||
|
||||
use crate::error::{Result, TwonlyError};
|
||||
use crate::{callback_generator, frb_generated::StreamSink};
|
||||
|
|
@ -50,7 +50,9 @@ pub(crate) fn get_callbacks() -> Result<FlutterCallbacks> {
|
|||
let caller_opt = CURRENT_CALLBACK_ID.try_with(|&c| c).ok();
|
||||
|
||||
let lock = FLUTTER_CALLBACKS.read().unwrap();
|
||||
let map = lock.as_ref().ok_or(TwonlyError::MissingCallbackInitialization)?;
|
||||
let map = lock
|
||||
.as_ref()
|
||||
.ok_or(TwonlyError::MissingCallbackInitialization)?;
|
||||
|
||||
if let Some(id) = caller_opt {
|
||||
if let Some(cb) = map.get(&id) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use crate::bridge::callbacks::get_callbacks;
|
||||
use crate::bridge::get_twonly_flutter;
|
||||
use crate::error::TwonlyError;
|
||||
use protocols::user_discovery::error::{Result, UserDiscoveryError};
|
||||
use protocols::user_discovery::traits::UserDiscoveryUtils;
|
||||
use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryStore};
|
||||
use crate::user_discovery::error::{Result, UserDiscoveryError};
|
||||
use crate::user_discovery::traits::UserDiscoveryUtils;
|
||||
use crate::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryStore};
|
||||
#[cfg(test)]
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -148,6 +149,7 @@ impl UserDiscoveryStore for UserDiscoveryStoreFlutter {
|
|||
.ok_or(TwonlyError::DartError.into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
async fn get_all_announced_users(
|
||||
&self,
|
||||
) -> Result<HashMap<AnnouncedUser, Vec<(i64, Option<i64>)>>> {
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ use crate::error::Result;
|
|||
use crate::error::TwonlyError;
|
||||
use crate::keys::KeyManager;
|
||||
use crate::secure_storage::SecureStorage;
|
||||
use crate::user_discovery::UserDiscovery;
|
||||
use crate::utils::Shared;
|
||||
use flutter_rust_bridge::frb;
|
||||
use protocols::user_discovery::UserDiscovery;
|
||||
|
||||
pub use protocols::user_discovery::traits::AnnouncedUser;
|
||||
pub use protocols::user_discovery::traits::OtherPromotion;
|
||||
pub use crate::user_discovery::traits::AnnouncedUser;
|
||||
pub use crate::user_discovery::traits::OtherPromotion;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct InitConfig {
|
||||
|
|
@ -57,9 +57,9 @@ pub(crate) struct TwonlyFlutter {
|
|||
pub(super) fn get_twonly_flutter() -> Result<&'static TwonlyFlutter> {
|
||||
let ctx = Context::get_static()?;
|
||||
if let Context::Flutter(twonly) = ctx {
|
||||
return Ok(twonly);
|
||||
Ok(twonly)
|
||||
} else {
|
||||
return Err(TwonlyError::Initialization);
|
||||
Err(TwonlyError::Initialization)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ impl RustBackupIdentity {
|
|||
|
||||
pub async fn get_identity_backup_bytes() -> Result<Vec<u8>> {
|
||||
let key_manager = get_twonly_flutter()?.key_manager.lock().await;
|
||||
return BackupIdentity::encrypt_key_manager(&key_manager);
|
||||
BackupIdentity::encrypt_key_manager(&key_manager)
|
||||
}
|
||||
|
||||
pub async fn restore_identity_backup(
|
||||
|
|
@ -70,7 +70,7 @@ impl RustBackupIdentity {
|
|||
impl RustBackupArchive {
|
||||
pub async fn create_backup_archive() -> Result<(String, String)> {
|
||||
let ctx = Context::get_static()?;
|
||||
let path = BackupArchive::create_backup(&ctx).await?;
|
||||
let path = BackupArchive::create_backup(ctx).await?;
|
||||
let key_manager = get_twonly_flutter()?.key_manager.lock().await;
|
||||
let token = hex::encode(key_manager.main_key.get_backup_download_token());
|
||||
Ok((token, path.canonicalize()?.to_string_lossy().to_string()))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::user_discovery::UserDiscovery;
|
||||
use crate::{
|
||||
bridge::{
|
||||
callbacks::user_discovery::{UserDiscoveryStoreFlutter, UserDiscoveryUtilsFlutter},
|
||||
|
|
@ -9,7 +10,6 @@ use crate::{
|
|||
log::init_tracing,
|
||||
utils::Shared,
|
||||
};
|
||||
use protocols::user_discovery::UserDiscovery;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use tokio::sync::{Mutex, OnceCell};
|
||||
use zeroize::Zeroize;
|
||||
|
|
|
|||
|
|
@ -88,11 +88,10 @@ macro_rules! generate_test_select {
|
|||
#[cfg(test)]
|
||||
#[tokio::test]
|
||||
async fn [<test_ $select_fn>]() {
|
||||
use crate::database::Database;
|
||||
use tempfile::tempdir;
|
||||
let dir = tempdir().unwrap();
|
||||
let db_path = dir.path().join("test.sqlite").display().to_string();
|
||||
let db = Database::new(&db_path, None, false).await.unwrap();
|
||||
let db = crate::database::Database::new(&db_path, None, false).await.unwrap();
|
||||
db.run_migrations().await.unwrap();
|
||||
|
||||
$struct::$insert_fn(&db.pool, $($arg),+).await.unwrap();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::user_discovery::error::UserDiscoveryError;
|
||||
use hex::FromHexError;
|
||||
use protocols::user_discovery::error::UserDiscoveryError;
|
||||
use scrypt::errors::{InvalidOutputLen, InvalidParams};
|
||||
use thiserror::Error;
|
||||
use zip::result::ZipError;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ impl MainKey {
|
|||
self.decrypt_with_info(b"backup_key", encrypted_backup)
|
||||
}
|
||||
|
||||
/// Encrypts a newly generated media key using the derived Media Main Key.
|
||||
// Encrypts a newly generated media key using the derived Media Main Key.
|
||||
// pub fn encrypt_media_key(&self, media_key: &[u8; 32]) -> Vec<u8> {
|
||||
// self.encrypt_with_info(b"media_main_key", media_key)
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ mod error;
|
|||
mod frb_generated;
|
||||
mod keys;
|
||||
mod log;
|
||||
mod passwordless_recovery;
|
||||
mod secure_storage;
|
||||
mod standalone;
|
||||
mod user_discovery;
|
||||
mod utils;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ pub mod stores;
|
|||
pub mod tests;
|
||||
pub mod traits;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
#[cfg(test)]
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashSet};
|
||||
use std::sync::Arc;
|
||||
use std::u8;
|
||||
use blahaj::{Share, Sharks};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -184,6 +185,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
|||
/// * `Ok(HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>)` - All connections the user has discovered
|
||||
/// * `Err(UserDiscoveryError)` - If there where erros in the store.
|
||||
///
|
||||
#[cfg(test)]
|
||||
pub async fn get_all_announced_users(
|
||||
&self,
|
||||
) -> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>> {
|
||||
|
|
@ -384,7 +386,8 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
|||
config.promotion_version += 1;
|
||||
new_promotion_version = config.promotion_version;
|
||||
announcement_version = config.announcement_version;
|
||||
}).await?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
let message = UserDiscoveryMessage {
|
||||
version: Some(UserDiscoveryVersion {
|
||||
|
|
@ -430,7 +433,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
|||
}
|
||||
.encode_to_vec();
|
||||
|
||||
let sharks = Sharks(config.threshold as u8);
|
||||
let sharks = Sharks(config.threshold);
|
||||
let dealer = sharks.dealer(&encrypted_announcement);
|
||||
|
||||
let mut shares: Vec<Vec<u8>> = dealer
|
||||
|
|
@ -476,10 +479,8 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
|||
where
|
||||
F: FnOnce(&mut UserDiscoveryConfig),
|
||||
{
|
||||
let _lock = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(10),
|
||||
self.config_lock.lock(),
|
||||
)
|
||||
let _lock =
|
||||
tokio::time::timeout(std::time::Duration::from_secs(10), self.config_lock.lock())
|
||||
.await
|
||||
.ok();
|
||||
let mut config: UserDiscoveryConfig =
|
||||
|
|
@ -539,9 +540,9 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
|||
.verify_stored_pubkey(contact_id, &signed_data.public_key)
|
||||
.await?
|
||||
{
|
||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
||||
"public key does not match with stored one",
|
||||
)));
|
||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(
|
||||
"public key does not match with stored one".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if !self
|
||||
|
|
@ -553,9 +554,9 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
|||
)
|
||||
.await?
|
||||
{
|
||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
||||
"signature invalid",
|
||||
)));
|
||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(
|
||||
"signature invalid".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Only add this user to the promotions if the users enabled this feature
|
||||
|
|
@ -567,7 +568,8 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
|||
config.promotion_version += 1;
|
||||
new_promotion_version = config.promotion_version;
|
||||
announcement_version = config.announcement_version;
|
||||
}).await?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
let message = UserDiscoveryMessage {
|
||||
version: Some(UserDiscoveryVersion {
|
||||
|
|
@ -624,11 +626,10 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
|||
self.store
|
||||
.push_new_user_relation(
|
||||
promotion.from_contact_id,
|
||||
announced_user,
|
||||
announced_user.clone(),
|
||||
promotion.public_key_verified_timestamp,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -731,9 +732,9 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
|||
)
|
||||
.await?
|
||||
{
|
||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
||||
"signature is invalid",
|
||||
)));
|
||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(
|
||||
"signature is invalid".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
tracing::debug!("Announcement valid.");
|
||||
|
|
@ -790,4 +791,3 @@ impl Default for UserDiscoveryConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ impl UserDiscoveryStore for InMemoryStore {
|
|||
async fn get_own_promotions_after_version(&self, version: u32) -> Result<Vec<Vec<u8>>> {
|
||||
let storage = self.storage();
|
||||
let elements = storage.own_promotions[(version as usize)..]
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|(_, promotion)| promotion.to_owned())
|
||||
.collect();
|
||||
Ok(elements)
|
||||
|
|
@ -107,7 +107,7 @@ impl UserDiscoveryStore for InMemoryStore {
|
|||
if let Some(element) = element {
|
||||
return Ok(Some(element.1.to_owned()));
|
||||
}
|
||||
return Ok(None);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn store_other_promotion(&self, promotion: OtherPromotion) -> Result<()> {
|
||||
|
|
@ -158,7 +158,7 @@ impl UserDiscoveryStore for InMemoryStore {
|
|||
let entry = storage
|
||||
.announced_users
|
||||
.entry(announced_user.clone())
|
||||
.or_insert(vec![]);
|
||||
.or_default();
|
||||
if announced_user.user_id != from_contact_id {
|
||||
if let Some(found) = entry.iter_mut().find(|x| x.0 == from_contact_id) {
|
||||
found.1 = public_key_verified_timestamp;
|
||||
4
rust/src/user_discovery/stores/mod.rs
Normal file
4
rust/src/user_discovery/stores/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#[cfg(test)]
|
||||
mod in_memory_store;
|
||||
#[cfg(test)]
|
||||
pub(super) use in_memory_store::InMemoryStore;
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
#[cfg(test)]
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::user_discovery::error::Result;
|
||||
use crate::user_discovery::UserID;
|
||||
use std::future::Future;
|
||||
|
||||
/// Type alias used in `UserDiscoveryStore::get_all_announced_users`.
|
||||
#[cfg(test)]
|
||||
pub type AnnouncedUserMap = HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>;
|
||||
|
||||
#[derive(Clone, sqlx::FromRow)]
|
||||
pub struct OtherPromotion {
|
||||
pub promotion_id: u32,
|
||||
|
|
@ -65,9 +70,8 @@ pub trait UserDiscoveryStore {
|
|||
public_key_verified_timestamp: Option<i64>,
|
||||
) -> impl Future<Output = Result<()>> + Send;
|
||||
|
||||
fn get_all_announced_users(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>>> + Send;
|
||||
#[cfg(test)]
|
||||
fn get_all_announced_users(&self) -> impl Future<Output = Result<AnnouncedUserMap>> + Send;
|
||||
|
||||
fn get_contact_promotion(
|
||||
&self,
|
||||
0
rust_dependencies/.gitignore
vendored
0
rust_dependencies/.gitignore
vendored
1639
rust_dependencies/Cargo.lock
generated
1639
rust_dependencies/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +0,0 @@
|
|||
[workspace]
|
||||
members = ["protocols"]
|
||||
resolver = "3"
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
[package]
|
||||
name = "protocols"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = "2.0.18"
|
||||
tracing = "0.1.44"
|
||||
serde = "1.0.228"
|
||||
prost = "0.14.1"
|
||||
rand = "0.10.1"
|
||||
blahaj = "0.6.0"
|
||||
serde_json = "1.0"
|
||||
base64 = "0.22.1"
|
||||
hmac = "0.13.0"
|
||||
sha2 = "0.11.0"
|
||||
tokio = { version = "1.44", features = ["full"] }
|
||||
sqlx = { version = "0.9.0-alpha.1", default-features = false, features = [
|
||||
"derive",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_env_logger = "0.5.0"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.14.1"
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
use std::io::Result;
|
||||
fn main() -> Result<()> {
|
||||
prost_build::compile_protos(
|
||||
&[
|
||||
"src/user_discovery/types.proto",
|
||||
"src/key_verification/types.proto",
|
||||
],
|
||||
&["src/"],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
use prost::DecodeError;
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = core::result::Result<T, KeyVerificationError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum KeyVerificationError {
|
||||
#[error("The prefix deeplink url must start with https:// and end with a #")]
|
||||
InvalidDeeplinkPrefix,
|
||||
|
||||
#[error("Invalid qr text")]
|
||||
InvalidQrText,
|
||||
|
||||
#[error(
|
||||
"Contact user_id is known and the stored public_key does not match the received user id"
|
||||
)]
|
||||
InvalidPublicKeyAndUserIdCombination,
|
||||
|
||||
#[error("Store error: `{0}`")]
|
||||
Store(String),
|
||||
|
||||
#[error("`{0}`")]
|
||||
Base64(#[from] base64::DecodeError),
|
||||
|
||||
#[error("`{0}`")]
|
||||
Prost(#[from] DecodeError),
|
||||
|
||||
#[error("`{0}`")]
|
||||
Hmac(#[from] hmac::digest::InvalidLength),
|
||||
}
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
use crate::key_verification::{error::KeyVerificationError, traits::KeyVerificationStore};
|
||||
use crate::user_discovery::UserID;
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
||||
use error::Result;
|
||||
use hmac::{Hmac, KeyInit, Mac};
|
||||
use prost::Message;
|
||||
use sha2::Sha256;
|
||||
|
||||
pub(crate) mod error;
|
||||
pub mod stores;
|
||||
pub mod traits;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/key_verification.rs"));
|
||||
|
||||
pub struct KeyVerificationConfig {
|
||||
/// The link prefix for the qr code which should be registered as a deeplink on Android and a universal link on iOS
|
||||
/// The link MUST start with a https:// and end with a #
|
||||
/// The link should contain the username of the user so the application can show the scanned user without internet
|
||||
/// Example: https://me.twonly.eu/tobi#
|
||||
deeplink_prefix: String,
|
||||
/// The user ID used to calculate the verification proof
|
||||
user_id: UserID,
|
||||
/// The public_key of the user to calculate the verification proof
|
||||
public_key: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct ScannedUser {
|
||||
pub user_id: UserID,
|
||||
pub public_key: Vec<u8>,
|
||||
pub verification_proof: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct KeyVerification<Store: KeyVerificationStore> {
|
||||
store: Store,
|
||||
config: KeyVerificationConfig,
|
||||
}
|
||||
|
||||
impl<Store: KeyVerificationStore> KeyVerification<Store> {
|
||||
pub fn new(store: Store, config: KeyVerificationConfig) -> Result<KeyVerification<Store>> {
|
||||
if !config.deeplink_prefix.starts_with("https://") || !config.deeplink_prefix.ends_with("#")
|
||||
{
|
||||
return Err(KeyVerificationError::InvalidDeeplinkPrefix);
|
||||
}
|
||||
Ok(Self { store, config })
|
||||
}
|
||||
|
||||
/// Generates the a string which should be displayed in the UI so others can scan it.
|
||||
pub fn generate_qr_text(&self) -> Result<String> {
|
||||
// 10 Bytes should be enough. Tokens are only valid for one day and then deleted.
|
||||
let secret_verification_token: Vec<u8> = rand::random_iter().take(16).collect();
|
||||
|
||||
self.store
|
||||
.push_new_secret_verification_token(&secret_verification_token)?;
|
||||
|
||||
let verification_data = VerificationData {
|
||||
user_id: self.config.user_id,
|
||||
public_key: self.config.public_key.clone(),
|
||||
secret_verification_token,
|
||||
};
|
||||
|
||||
let verification_data_bytes = verification_data.encode_to_vec();
|
||||
let encoded = URL_SAFE_NO_PAD.encode(verification_data_bytes);
|
||||
|
||||
Ok(format!("{}{}", self.config.deeplink_prefix, encoded))
|
||||
}
|
||||
|
||||
/// Handles the scanned qr code text and creates a response message
|
||||
/// which can be send to the other person
|
||||
pub fn get_user_from_scanned_qr_text(&self, received_text: &str) -> Result<ScannedUser> {
|
||||
let splitted: Vec<_> = received_text.split('#').collect();
|
||||
if splitted.len() != 2 {
|
||||
tracing::info!("Scanned qr text does not contain a #");
|
||||
return Err(KeyVerificationError::InvalidQrText);
|
||||
}
|
||||
let verification_data_bytes = URL_SAFE_NO_PAD.decode(splitted[1])?;
|
||||
let verification_data = VerificationData::decode(verification_data_bytes.as_slice())?;
|
||||
|
||||
let mut mac = Hmac::<Sha256>::new_from_slice(&verification_data.secret_verification_token)?;
|
||||
mac.update(&self.config.user_id.to_le_bytes());
|
||||
mac.update(&self.config.public_key);
|
||||
mac.update(&verification_data.user_id.to_le_bytes());
|
||||
mac.update(&verification_data.public_key);
|
||||
|
||||
let verification_proof = mac.finalize().into_bytes().to_vec();
|
||||
|
||||
Ok(ScannedUser {
|
||||
user_id: verification_data.user_id,
|
||||
public_key: verification_data.public_key,
|
||||
verification_proof,
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks whether the received verification proof is valid
|
||||
pub fn is_received_verification_proof_valid(
|
||||
&self,
|
||||
from_user_id: UserID,
|
||||
public_key: Vec<u8>,
|
||||
verification_proof: Vec<u8>,
|
||||
) -> Result<bool> {
|
||||
let verification_tokens = self.store.get_all_valid_verification_tokens()?;
|
||||
|
||||
for verification_token in &verification_tokens {
|
||||
let calculated_verification_proof = {
|
||||
let mut mac = Hmac::<Sha256>::new_from_slice(verification_token)?;
|
||||
mac.update(&from_user_id.to_le_bytes());
|
||||
mac.update(&public_key);
|
||||
mac.update(&self.config.user_id.to_le_bytes());
|
||||
mac.update(&self.config.public_key);
|
||||
mac.finalize().into_bytes().to_vec()
|
||||
};
|
||||
|
||||
if calculated_verification_proof == verification_proof {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::key_verification::{stores::InMemoryStore, KeyVerification, KeyVerificationConfig};
|
||||
|
||||
#[test]
|
||||
fn test_key_verification() {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
const ALICE_ID: i64 = 10;
|
||||
const BOB_ID: i64 = 11;
|
||||
|
||||
let alice_kv = KeyVerification::new(
|
||||
InMemoryStore::default(),
|
||||
KeyVerificationConfig {
|
||||
user_id: ALICE_ID,
|
||||
public_key: vec![ALICE_ID as u8; 32],
|
||||
deeplink_prefix: "https://me.twonly.eu/alice#".into(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let bob_kv = KeyVerification::new(
|
||||
InMemoryStore::default(),
|
||||
KeyVerificationConfig {
|
||||
user_id: BOB_ID,
|
||||
public_key: vec![BOB_ID as u8; 32],
|
||||
deeplink_prefix: "https://me.twonly.eu/bob#".into(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let qr_code_text = alice_kv.generate_qr_text().unwrap();
|
||||
assert_eq!(qr_code_text.len(), 99);
|
||||
|
||||
tracing::debug!("Generated QR-Code-Link: {qr_code_text}");
|
||||
|
||||
let scanned_user = bob_kv.get_user_from_scanned_qr_text(&qr_code_text).unwrap();
|
||||
|
||||
// THIS must be done by the application
|
||||
assert_eq!(scanned_user.user_id, ALICE_ID);
|
||||
assert_eq!(scanned_user.public_key, vec![ALICE_ID as u8; 32]);
|
||||
|
||||
// SEND scanned_user.verification_proof over the establish e2ee protected session if public_key verification was valid.
|
||||
|
||||
let valid_verification_proof = alice_kv
|
||||
.is_received_verification_proof_valid(
|
||||
BOB_ID,
|
||||
vec![BOB_ID as u8; 32],
|
||||
scanned_user.verification_proof.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(valid_verification_proof, true);
|
||||
|
||||
let valid_verification_proof = alice_kv
|
||||
.is_received_verification_proof_valid(
|
||||
BOB_ID,
|
||||
vec![(BOB_ID + 1) as u8; 32],
|
||||
scanned_user.verification_proof.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(valid_verification_proof, false);
|
||||
|
||||
let mut modified_proof = scanned_user.verification_proof;
|
||||
modified_proof[0] = modified_proof[0] + 1;
|
||||
|
||||
let valid_verification_proof = alice_kv
|
||||
.is_received_verification_proof_valid(BOB_ID, vec![BOB_ID as u8; 32], modified_proof)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(valid_verification_proof, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::key_verification::{error::Result, traits::KeyVerificationStore};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InMemoryStore {
|
||||
verification_tokens: Arc<Mutex<Vec<Vec<u8>>>>,
|
||||
}
|
||||
|
||||
impl KeyVerificationStore for InMemoryStore {
|
||||
fn push_new_secret_verification_token(&self, token: &[u8]) -> Result<()> {
|
||||
self.verification_tokens
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(token.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_all_valid_verification_tokens(&self) -> Result<Vec<Vec<u8>>> {
|
||||
Ok(self.verification_tokens.lock().unwrap().clone())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
mod in_memory_store;
|
||||
|
||||
pub use in_memory_store::InMemoryStore;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
use super::error::Result;
|
||||
pub trait KeyVerificationStore {
|
||||
fn push_new_secret_verification_token(&self, token: &[u8]) -> Result<()>;
|
||||
/// This function should return all tokens from the last 24h
|
||||
/// All other tokens can be removed from the database
|
||||
fn get_all_valid_verification_tokens(&self) -> Result<Vec<Vec<u8>>>;
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package key_verification;
|
||||
|
||||
message VerificationData {
|
||||
int64 user_id = 1;
|
||||
bytes public_key = 2;
|
||||
bytes secret_verification_token = 3;
|
||||
}
|
||||
|
||||
message VerificationMessage {
|
||||
bytes calculated_mac = 1;
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
pub mod key_verification;
|
||||
pub mod passwordless_recovery;
|
||||
pub mod user_discovery;
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
mod in_memory_store;
|
||||
pub use in_memory_store::InMemoryStore;
|
||||
|
|
@ -18,7 +18,7 @@ protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "qr.proto"
|
|||
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "data.proto"
|
||||
|
||||
mkdir "$GENERATED_DIR/user_discovery/" &>/dev/null
|
||||
protoc --proto_path="./rust_dependencies/protocols/src/user_discovery/" --dart_out="$GENERATED_DIR/user_discovery/" "types.proto"
|
||||
protoc --proto_path="./rust/src/user_discovery/" --dart_out="$GENERATED_DIR/user_discovery/" "types.proto"
|
||||
|
||||
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "push_notification.proto"
|
||||
protoc --proto_path="$CLIENT_DIR" --swift_out="./ios/NotificationService/" "push_notification.proto"
|
||||
|
|
|
|||
Loading…
Reference in a new issue