From b92ce0b0fa89dfc414274d767d555ee70fabec7e Mon Sep 17 00:00:00 2001 From: StNicolay Date: Mon, 24 Apr 2023 20:48:34 +0300 Subject: [PATCH] First prototype --- Cargo.lock | 79 +++++++++++ Cargo.toml | 6 +- .../src/m20220101_000001_create_table.rs | 4 +- src/entity/account.rs | 2 +- src/entity/master_pass.rs | 2 +- src/main.rs | 127 +++++++++++++++++- 6 files changed, 213 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb5bac0..a025b4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + [[package]] name = "aliasable" version = "0.1.3" @@ -55,6 +64,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + [[package]] name = "aquamarine" version = "0.1.12" @@ -235,6 +250,17 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -744,6 +770,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "erasable" version = "1.2.1" @@ -1032,6 +1071,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -1096,6 +1144,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "hyper" version = "0.14.26" @@ -1596,12 +1653,16 @@ dependencies = [ name = "pass_manager" version = "0.1.0" dependencies = [ + "anyhow", "chacha20poly1305", "dotenv", + "log", "migration", "pbkdf2", + "pretty_env_logger", "scrypt", "sea-orm", + "sha2", "teloxide", "thiserror", "tokio", @@ -1742,6 +1803,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1804,6 +1875,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.26" @@ -1876,6 +1953,8 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax 0.7.1", ] diff --git a/Cargo.toml b/Cargo.toml index 4cf1f36..c61ba0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,16 @@ strip = true members = [".", "migration"] [dependencies] -chacha20poly1305 = "0.10.1" +anyhow = "1.0.70" +chacha20poly1305 = { version = "0.10.1", features = ["std"] } dotenv = "0.15.0" +log = "0.4.17" migration = { version = "0.1.0", path = "migration" } pbkdf2 = "0.12.1" +pretty_env_logger = "0.4.0" scrypt = "0.11.0" sea-orm = { version = "0.11.2", features = ["sqlx-mysql", "runtime-tokio-rustls"] } +sha2 = "0.10.6" teloxide = { version = "0.12.2", features = ["macros"] } thiserror = "1.0.40" tokio = { version = "1.27.0", features = ["macros"] } diff --git a/migration/src/m20220101_000001_create_table.rs b/migration/src/m20220101_000001_create_table.rs index 652da3c..360873d 100644 --- a/migration/src/m20220101_000001_create_table.rs +++ b/migration/src/m20220101_000001_create_table.rs @@ -36,7 +36,7 @@ impl MigrationTrait for Migration { .if_not_exists() .col( ColumnDef::new(MasterPass::UserId) - .integer() + .big_unsigned() .primary_key() .not_null(), ) @@ -54,7 +54,7 @@ impl MigrationTrait for Migration { Table::create() .table(Account::Table) .if_not_exists() - .col(ColumnDef::new(Account::UserId).integer().not_null()) + .col(ColumnDef::new(Account::UserId).big_unsigned().not_null()) .col(ColumnDef::new(Account::Name).string_len(256).not_null()) .col(ColumnDef::new(Account::Salt).binary_len(64).not_null()) .col(ColumnDef::new(Account::EncLogin).var_binary(256).not_null()) diff --git a/src/entity/account.rs b/src/entity/account.rs index 724f7eb..d06212e 100644 --- a/src/entity/account.rs +++ b/src/entity/account.rs @@ -6,7 +6,7 @@ use sea_orm::entity::prelude::*; #[sea_orm(table_name = "account")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] - pub user_id: i32, + pub user_id: u64, #[sea_orm(primary_key, auto_increment = false)] pub name: String, #[sea_orm(column_type = "Binary(BlobSize::Blob(Some(64)))")] diff --git a/src/entity/master_pass.rs b/src/entity/master_pass.rs index 453266b..87928ce 100644 --- a/src/entity/master_pass.rs +++ b/src/entity/master_pass.rs @@ -6,7 +6,7 @@ use sea_orm::entity::prelude::*; #[sea_orm(table_name = "master_pass")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] - pub user_id: i32, + pub user_id: u64, #[sea_orm(column_type = "Binary(BlobSize::Blob(Some(64)))")] pub salt: Vec, #[sea_orm(column_type = "Binary(BlobSize::Blob(Some(128)))")] diff --git a/src/main.rs b/src/main.rs index 6686bd0..543ecc3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,126 @@ -fn main() { - println!("Hello, world!") +mod entity; + +use entity::{account, prelude::Account}; + +use anyhow::Result; +use chacha20poly1305::{ + aead::{rand_core::RngCore, Aead, AeadCore, OsRng}, + ChaCha20Poly1305, KeyInit, +}; +use dotenv::dotenv; +use pbkdf2::pbkdf2_hmac_array; +use sea_orm::{ + ActiveModelTrait, ActiveValue::Set, ColumnTrait, ConnectOptions, Database, EntityTrait, + QueryFilter, +}; +use sha2::Sha256; +use std::env; +use teloxide::{prelude::*, utils::command::BotCommands}; + +#[derive(BotCommands, Clone)] +#[command(rename_rule = "lowercase")] +enum Command { + #[command()] + Help, + #[command(parse_with = "split")] + AddAccount(String, String, String, String), + #[command(parse_with = "split")] + GetAccount(String, String), +} + +use Command::*; + +struct Cipher { + chacha: ChaCha20Poly1305, +} + +impl Cipher { + fn new(password: &[u8], salt: &[u8]) -> Self { + let key = pbkdf2_hmac_array::(password, salt, 480000); + + Self { + chacha: ChaCha20Poly1305::new(&key.into()), + } + } + + fn encrypt(&self, value: &[u8]) -> Result> { + let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng); + let mut result = self.chacha.encrypt(&nonce, value).unwrap(); + result.extend(nonce); + Ok(result) + } + + fn decrypt(&self, value: &[u8]) -> Result> { + let (data, nonce) = value.split_at(value.len() - 12); + assert!(nonce.len() == 12); + self.chacha + .decrypt(nonce.into(), data) + .map_err(|err| err.into()) + } +} + +async fn answer_command(db: ConnectOptions, bot: Bot, msg: Message, cmd: Command) -> Result<()> { + let user_id = msg.from().unwrap().id.0; + let db = Database::connect(db).await?; + match cmd { + Help => { + bot.send_message(msg.chat.id, Command::descriptions().to_string()) + .await?; + } + AddAccount(name, login, password, master_pass) => { + let mut salt = vec![0; 64]; + OsRng.fill_bytes(&mut salt); + let cipher = Cipher::new(master_pass.as_ref(), &salt); + let enc_login = Set(cipher.encrypt(login.as_ref())?); + let enc_password = Set(cipher.encrypt(password.as_ref())?); + let account = account::ActiveModel { + name: Set(name), + user_id: Set(user_id), + salt: Set(salt), + enc_login, + enc_password, + }; + account.insert(&db).await?; + bot.send_message(msg.chat.id, "Success").await?; + } + GetAccount(name, master_pass) => { + let account = Account::find() + .filter( + account::Column::UserId + .eq(user_id) + .add(account::Column::Name.eq(&name)), + ) + .one(&db) + .await? + .unwrap(); + let cipher = Cipher::new(master_pass.as_ref(), &account.salt); + let login = String::from_utf8(cipher.decrypt(&account.enc_login)?)?; + let password = String::from_utf8(cipher.decrypt(&account.enc_password)?)?; + let message = format!("Account `{name}`\nLogin: `{login}`\nPassword: `{password}`"); + bot.send_message(msg.chat.id, message).await?; + } + } + Ok(()) +} + +#[tokio::main] +async fn main() { + let _ = dotenv(); + pretty_env_logger::init(); + + let token = env::var("TOKEN").unwrap(); + let database_url = env::var("DATABASE_URL").unwrap(); + let bot = Bot::new(token); + let db = ConnectOptions::new(database_url); + Dispatcher::builder( + bot, + Update::filter_message() + .filter_command::() + .endpoint(answer_command), + ) + .dependencies(dptree::deps![db]) + .enable_ctrlc_handler() + .build() + .dispatch() + .await; }