feat: use dpapi for windows to ensure longer passwords work
This commit is contained in:
7
src-tauri/Cargo.lock
generated
7
src-tauri/Cargo.lock
generated
@@ -1116,6 +1116,7 @@ dependencies = [
|
|||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"tauri-plugin-sql",
|
"tauri-plugin-sql",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"widestring",
|
||||||
"winapi",
|
"winapi",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
"winreg 0.55.0",
|
"winreg 0.55.0",
|
||||||
@@ -5799,6 +5800,12 @@ dependencies = [
|
|||||||
"wasite",
|
"wasite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "widestring"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@@ -41,6 +41,7 @@ once_cell = "1.21.3"
|
|||||||
winreg = "0.55.0"
|
winreg = "0.55.0"
|
||||||
winapi = { version = "0.3", features = ["winuser", "wincrypt", "memoryapi", "winbase", "dpapi"] }
|
winapi = { version = "0.3", features = ["winuser", "wincrypt", "memoryapi", "winbase", "dpapi"] }
|
||||||
base64 = "0.21"
|
base64 = "0.21"
|
||||||
|
widestring = "1.0"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-single-instance = "2.3.0"
|
tauri-plugin-single-instance = "2.3.0"
|
||||||
|
@@ -13,9 +13,9 @@ use tokio::time::{Duration, sleep};
|
|||||||
|
|
||||||
use crate::presence;
|
use crate::presence;
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
check_folder_completeness, get_osu_config, get_osu_user_config, get_window_title_by_pid,
|
check_folder_completeness, encrypt_password, get_osu_config, get_osu_user_config,
|
||||||
is_net8_installed, is_osuwinello_available, is_wmctrl_available, set_osu_config_vals,
|
get_window_title_by_pid, is_net8_installed, is_osuwinello_available, is_wmctrl_available,
|
||||||
set_osu_user_config_vals,
|
set_osu_config_vals, set_osu_user_config_vals,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -719,3 +719,25 @@ pub fn has_osuwinello() -> bool {
|
|||||||
pub async fn has_net8() -> bool {
|
pub async fn has_net8() -> bool {
|
||||||
is_net8_installed().await
|
is_net8_installed().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* osu!.exe decompile result:
|
||||||
|
* dpapi cipher is most likely: cu24180ncjeiu0ci1nwui
|
||||||
|
* dpapi key type is: 1
|
||||||
|
*
|
||||||
|
* example call for encryption: DPAPI.Encrypt((DPAPI.KeyType)1, this.storage.UnsecureRepresentation(), "cu24180ncjeiu0ci1nwui", this.representation.ToString());
|
||||||
|
* the method args are following: Encrypt(DPAPI.KeyType keyType, byte[] plainTextBytes, byte[] entropyBytes, string description)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn encrypt_string(string: String, entropy: String) -> String {
|
||||||
|
let encrypted = encrypt_password(&string, &entropy);
|
||||||
|
|
||||||
|
match encrypted {
|
||||||
|
Ok(encrypted_vec) => {
|
||||||
|
// encrypted_vec is already a String, not Vec<u8>
|
||||||
|
encrypted_vec
|
||||||
|
}
|
||||||
|
Err(_) => string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -5,18 +5,17 @@ pub mod commands;
|
|||||||
pub mod presence;
|
pub mod presence;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
use crate::commands::{
|
use crate::commands::{
|
||||||
check_for_corruption, download_ezpp_launcher_update_files, exit, find_osu_installation,
|
check_for_corruption, download_ezpp_launcher_update_files, encrypt_string, exit,
|
||||||
get_beatmapsets_count, get_ezpp_launcher_update_files, get_hwid, get_launcher_version,
|
find_osu_installation, get_beatmapsets_count, get_ezpp_launcher_update_files, get_hwid,
|
||||||
get_osu_release_stream, get_osu_skin, get_osu_version, get_platform, get_skins_count,
|
get_launcher_version, get_osu_release_stream, get_osu_skin, get_osu_version, get_platform,
|
||||||
is_osu_running, open_url_in_browser, presence_connect, presence_disconnect,
|
get_skins_count, has_net8, has_osuwinello, has_wmctrl, is_osu_running, open_url_in_browser,
|
||||||
presence_is_connected, presence_update_status, presence_update_user, replace_ui_files, run_osu,
|
presence_connect, presence_disconnect, presence_is_connected, presence_update_status,
|
||||||
run_osu_updater, set_osu_config_values, set_osu_user_config_values, valid_osu_folder, has_osuwinello, has_wmctrl,
|
presence_update_user, replace_ui_files, run_osu, run_osu_updater, set_osu_config_values,
|
||||||
has_net8
|
set_osu_user_config_values, valid_osu_folder,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||||
@@ -61,7 +60,8 @@ pub fn run() {
|
|||||||
presence_is_connected,
|
presence_is_connected,
|
||||||
has_osuwinello,
|
has_osuwinello,
|
||||||
has_wmctrl,
|
has_wmctrl,
|
||||||
has_net8
|
has_net8,
|
||||||
|
encrypt_string
|
||||||
])
|
])
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
@@ -311,14 +311,19 @@ pub async fn is_net8_installed() -> bool {
|
|||||||
//TODO: maybe switch to this crate: https://crates.io/crates/windows-dpapi
|
//TODO: maybe switch to this crate: https://crates.io/crates/windows-dpapi
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn encrypt_password(password: &str) -> Result<String, String> {
|
pub fn encrypt_password(password: &str, entropy: &str) -> Result<String, String> {
|
||||||
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::os::windows::ffi::OsStrExt;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use winapi::shared::minwindef::{BYTE, DWORD, LPVOID};
|
use winapi::shared::minwindef::{BYTE, DWORD, LPVOID};
|
||||||
|
use winapi::shared::ntdef::LPCWSTR;
|
||||||
use winapi::um::dpapi::{CRYPTPROTECT_UI_FORBIDDEN, CryptProtectData};
|
use winapi::um::dpapi::{CRYPTPROTECT_UI_FORBIDDEN, CryptProtectData};
|
||||||
use winapi::um::winbase::LocalFree;
|
use winapi::um::winbase::LocalFree;
|
||||||
use winapi::um::wincrypt::DATA_BLOB;
|
use winapi::um::wincrypt::DATA_BLOB;
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
|
||||||
|
let description = "Encrypted";
|
||||||
|
|
||||||
let password_bytes = password.as_bytes();
|
let password_bytes = password.as_bytes();
|
||||||
let mut input_blob = DATA_BLOB {
|
let mut input_blob = DATA_BLOB {
|
||||||
@@ -326,16 +331,28 @@ pub fn encrypt_password(password: &str) -> Result<String, String> {
|
|||||||
pbData: password_bytes.as_ptr() as *mut BYTE,
|
pbData: password_bytes.as_ptr() as *mut BYTE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let entropy_bytes = entropy.as_bytes();
|
||||||
|
let mut entropy_blob = DATA_BLOB {
|
||||||
|
cbData: entropy_bytes.len() as DWORD,
|
||||||
|
pbData: entropy_bytes.as_ptr() as *mut BYTE,
|
||||||
|
};
|
||||||
|
|
||||||
let mut output_blob = DATA_BLOB {
|
let mut output_blob = DATA_BLOB {
|
||||||
cbData: 0,
|
cbData: 0,
|
||||||
pbData: ptr::null_mut(),
|
pbData: ptr::null_mut(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let wide_description: Vec<u16> = OsStrExt::encode_wide(OsStr::new(description))
|
||||||
|
.chain(Some(0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let p_description: LPCWSTR = wide_description.as_ptr();
|
||||||
|
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
CryptProtectData(
|
CryptProtectData(
|
||||||
&mut input_blob,
|
&mut input_blob,
|
||||||
ptr::null(),
|
p_description,
|
||||||
ptr::null_mut(),
|
&mut entropy_blob,
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
CRYPTPROTECT_UI_FORBIDDEN,
|
CRYPTPROTECT_UI_FORBIDDEN,
|
||||||
@@ -354,66 +371,7 @@ pub fn encrypt_password(password: &str) -> Result<String, String> {
|
|||||||
LocalFree(output_blob.pbData as LPVOID);
|
LocalFree(output_blob.pbData as LPVOID);
|
||||||
}
|
}
|
||||||
|
|
||||||
let base64_string = general_purpose::STANDARD.encode(&encrypted_data);
|
let base64_string = general_purpose::STANDARD_NO_PAD.encode(&encrypted_data);
|
||||||
|
|
||||||
Ok(base64_string)
|
Ok(base64_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn decrypt_password(encrypted_password_base64: &str) -> Result<String, String> {
|
|
||||||
use std::ptr;
|
|
||||||
use std::slice;
|
|
||||||
use winapi::shared::minwindef::{BYTE, DWORD, LPVOID};
|
|
||||||
use winapi::um::dpapi::{CRYPTPROTECT_UI_FORBIDDEN, CryptUnprotectData};
|
|
||||||
use winapi::um::winbase::LocalFree;
|
|
||||||
use winapi::um::wincrypt::DATA_BLOB;
|
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
|
||||||
|
|
||||||
// 1. Decode the Base64 string back into raw bytes
|
|
||||||
let encrypted_password_bytes = general_purpose::STANDARD.decode(encrypted_password_base64)
|
|
||||||
.map_err(|e| format!("Base64 decoding failed: {}", e))?;
|
|
||||||
|
|
||||||
// 2. Prepare the input data structure
|
|
||||||
let mut input_blob = DATA_BLOB {
|
|
||||||
cbData: encrypted_password_bytes.len() as DWORD,
|
|
||||||
pbData: encrypted_password_bytes.as_ptr() as *mut BYTE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 3. Prepare the output data structure
|
|
||||||
let mut output_blob = DATA_BLOB {
|
|
||||||
cbData: 0,
|
|
||||||
pbData: ptr::null_mut(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 4. Call the Windows API function
|
|
||||||
let result = unsafe {
|
|
||||||
CryptUnprotectData(
|
|
||||||
&mut input_blob,
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
CRYPTPROTECT_UI_FORBIDDEN,
|
|
||||||
&mut output_blob,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 5. Check the result
|
|
||||||
if result == 0 {
|
|
||||||
return Err("CryptUnprotectData failed".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Copy the decrypted data into a safe Rust Vec
|
|
||||||
let decrypted_bytes = unsafe {
|
|
||||||
slice::from_raw_parts(output_blob.pbData, output_blob.cbData as usize).to_vec()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 7. Free the memory allocated by the Windows API
|
|
||||||
unsafe {
|
|
||||||
LocalFree(output_blob.pbData as LPVOID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. Convert the decrypted bytes back to a UTF-8 string
|
|
||||||
String::from_utf8(decrypted_bytes)
|
|
||||||
.map_err(|e| format!("UTF-8 conversion failed: {}", e))
|
|
||||||
}
|
|
||||||
|
@@ -139,3 +139,5 @@ export const hasOsuWinello = async () =>
|
|||||||
await invoke<boolean>('has_osuwinello');
|
await invoke<boolean>('has_osuwinello');
|
||||||
export const hasNet8 = async () =>
|
export const hasNet8 = async () =>
|
||||||
await invoke<boolean>('has_net8');
|
await invoke<boolean>('has_net8');
|
||||||
|
export const encryptString = async (str: string, entropy: string) =>
|
||||||
|
await invoke<string>('encrypt_string', {string: str, entropy});
|
@@ -83,6 +83,7 @@
|
|||||||
import { osuapi } from '@/api/osuapi';
|
import { osuapi } from '@/api/osuapi';
|
||||||
import {
|
import {
|
||||||
downloadEZPPLauncherUpdateFiles,
|
downloadEZPPLauncherUpdateFiles,
|
||||||
|
encryptString,
|
||||||
exit,
|
exit,
|
||||||
getBeatmapSetsCount,
|
getBeatmapSetsCount,
|
||||||
getEZPPLauncherUpdateFiles,
|
getEZPPLauncherUpdateFiles,
|
||||||
@@ -296,7 +297,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Password',
|
key: 'Password',
|
||||||
value: password,
|
value: $platform === "windows" ? await encryptString(password, "cu24180ncjeiu0ci1nwui") : password,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'SaveUsername',
|
key: 'SaveUsername',
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
import { Toaster } from '@/components/ui/sonner';
|
import { Toaster } from '@/components/ui/sonner';
|
||||||
import { userAuth } from '@/userAuthentication';
|
import { userAuth } from '@/userAuthentication';
|
||||||
import { exit, getLauncherVersion, getPlatform } from '@/osuUtil';
|
import { encryptString, exit, getLauncherVersion, getPlatform } from '@/osuUtil';
|
||||||
import Button from '@/components/ui/button/button.svelte';
|
import Button from '@/components/ui/button/button.svelte';
|
||||||
import * as presence from '@/presence';
|
import * as presence from '@/presence';
|
||||||
|
|
||||||
@@ -88,6 +88,13 @@
|
|||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
window.Buffer = Buffer;
|
window.Buffer = Buffer;
|
||||||
|
|
||||||
|
/* const decryptTest = "AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAs463WdMtG0mr/mVLQCZ6dQAAAAAIAAAAUgBhAHcAAAAQZgAAAAEAACAAAABnyhFo8QK2iL5aTehKmsZSUpihGWBwlKfYj7cL2/lDagAAAAAOgAAAAAIAACAAAABMxyG6EdkLORSMB8isbltZhPQV2iVQ2r+yJLJ2Tw9yWiAAAABE1EzBPD9PRFQ3evk0vneNpfQTNTvMpjDVUTl3kAaKWUAAAADBshJzPDx6qcYfYh2zh6cKJWClZpIp6H50IKriW936XGhLFQboK/m18O77TBnpSrs0YosFjzsYVJTkrWjetFAf";
|
||||||
|
const decrypted = await decryptString(decryptTest);
|
||||||
|
console.log(decrypted); */
|
||||||
|
|
||||||
|
const encrypted = await encryptString("Todesengel007008009!", "cu24180ncjeiu0ci1nwui");
|
||||||
|
console.log(encrypted);
|
||||||
|
|
||||||
disableReload();
|
disableReload();
|
||||||
setupValues();
|
setupValues();
|
||||||
launcherVersion.set(await getLauncherVersion());
|
launcherVersion.set(await getLauncherVersion());
|
||||||
|
Reference in New Issue
Block a user