diff --git a/util/RustSdk/RustSdkException.cs b/util/RustSdk/RustSdkException.cs new file mode 100644 index 0000000000..52cbc3635f --- /dev/null +++ b/util/RustSdk/RustSdkException.cs @@ -0,0 +1,19 @@ +namespace Bit.RustSDK; + +/// +/// Exception thrown when the Rust SDK operations fail +/// +public class RustSdkException : Exception +{ + public RustSdkException() : base("An error occurred in the Rust SDK operation") + { + } + + public RustSdkException(string message) : base(message) + { + } + + public RustSdkException(string message, Exception innerException) : base(message, innerException) + { + } +} diff --git a/util/RustSdk/RustSdkService.cs b/util/RustSdk/RustSdkService.cs new file mode 100644 index 0000000000..dcb139dd0f --- /dev/null +++ b/util/RustSdk/RustSdkService.cs @@ -0,0 +1,83 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace Bit.RustSDK; + +/// +/// Service implementation that provides a C# friendly interface to the Rust SDK +/// +public class RustSdkService +{ + /// + /// Adds two integers using the native implementation + /// + /// First integer + /// Second integer + /// The sum of x and y + public int Add(int x, int y) + { + try + { + return NativeMethods.my_add(x, y); + } + catch (Exception ex) + { + throw new RustSdkException($"Failed to perform addition operation: {ex.Message}", ex); + } + } + + /// + /// Hashes a password using the native implementation + /// + /// User email + /// User password + /// The hashed password as a string + /// Thrown when email or password is null + /// Thrown when email or password is empty + /// Thrown when the native operation fails + public unsafe string HashPassword(string email, string password) + { + // Convert strings to null-terminated byte arrays + var emailBytes = Encoding.UTF8.GetBytes(email + '\0'); + var passwordBytes = Encoding.UTF8.GetBytes(password + '\0'); + + try + { + fixed (byte* emailPtr = emailBytes) + fixed (byte* passwordPtr = passwordBytes) + { + var resultPtr = NativeMethods.hash_password(emailPtr, passwordPtr); + + var result = TakeAndDestroyRustString(resultPtr); + + return result; + } + } + catch (RustSdkException) + { + throw; // Re-throw our custom exceptions + } + catch (Exception ex) + { + throw new RustSdkException($"Failed to hash password: {ex.Message}", ex); + } + } + + private static unsafe string TakeAndDestroyRustString(byte* ptr) + { + if (ptr == null) + { + throw new RustSdkException("Pointer is null"); + } + + var result = Marshal.PtrToStringUTF8((IntPtr)ptr); + NativeMethods.free_c_string(ptr); + + if (result == null) + { + throw new RustSdkException("Failed to convert native result to string"); + } + + return result; + } +} diff --git a/util/RustSdk/RustSdkServiceFactory.cs b/util/RustSdk/RustSdkServiceFactory.cs new file mode 100644 index 0000000000..5a1e44eb22 --- /dev/null +++ b/util/RustSdk/RustSdkServiceFactory.cs @@ -0,0 +1,30 @@ +namespace Bit.RustSDK; + +/// +/// Factory for creating Rust SDK service instances +/// +public static class RustSdkServiceFactory +{ + /// + /// Creates a new instance of the Rust SDK service + /// + /// A new IRustSdkService instance + public static RustSdkService Create() + { + return new RustSdkService(); + } + + /// + /// Creates a singleton instance of the Rust SDK service (thread-safe) + /// + /// A singleton IRustSdkService instance + public static RustSdkService CreateSingleton() + { + return SingletonHolder.Instance; + } + + private static class SingletonHolder + { + internal static readonly RustSdkService Instance = new RustSdkService(); + } +} diff --git a/util/RustSdk/rust/src/lib.rs b/util/RustSdk/rust/src/lib.rs index d6b2fcdfa4..1678944508 100644 --- a/util/RustSdk/rust/src/lib.rs +++ b/util/RustSdk/rust/src/lib.rs @@ -1,4 +1,49 @@ +use std::{ + ffi::{c_char, CStr, CString}, + num::NonZeroU32, +}; + +use bitwarden_crypto::{HashPurpose, Kdf, MasterKey}; + #[no_mangle] pub extern "C" fn my_add(x: i32, y: i32) -> i32 { x + y } + +/// # Safety +/// +/// The `email` and `password` pointers must be valid null-terminated C strings. +/// Both pointers must be non-null and point to valid memory for the duration of the function call. +#[no_mangle] +pub unsafe extern "C" fn hash_password( + email: *const c_char, + password: *const c_char, +) -> *const c_char { + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }; + + let email = CStr::from_ptr(email).to_str().unwrap(); + let password = CStr::from_ptr(password).to_str().unwrap(); + + let master_key = MasterKey::derive(password, email, &kdf).unwrap(); + + let res = master_key + .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) + .unwrap(); + + let res = CString::new(res).unwrap(); + + res.into_raw() +} + +/// # Safety +/// +/// The `str` pointer must be a valid pointer previously returned by `CString::into_raw` +/// and must not have already been freed. After calling this function, the pointer must not be used again. +#[no_mangle] +pub unsafe extern "C" fn free_c_string(str: *mut c_char) { + unsafe { + drop(CString::from_raw(str)); + } +} diff --git a/util/Seeder/Factories/UserSeeder.cs b/util/Seeder/Factories/UserSeeder.cs index 9c9d86e8d7..481f214637 100644 --- a/util/Seeder/Factories/UserSeeder.cs +++ b/util/Seeder/Factories/UserSeeder.cs @@ -8,12 +8,16 @@ public class UserSeeder { public static User CreateUser(string email) { + var nativeService = RustSdkServiceFactory.CreateSingleton(); Console.WriteLine(NativeMethods.my_add(2, 3)); + + var password = nativeService.HashPassword(email, "asdfasdfasdf"); + return new User { Id = Guid.NewGuid(), Email = email, - MasterPassword = "AQAAAAIAAYagAAAAEBATmF66OHMpHuHKc1CsGZQ1ltHUHyhYK+7e4re3bVFi16SOpLpDfzdFswnvFQs2Rg==", + MasterPassword = password, SecurityStamp = "4830e359-e150-4eae-be2a-996c81c5e609", Key = "2.z/eLKFhd62qy9RzXu3UHgA==|fF6yNupiCIguFKSDTB3DoqcGR0Xu4j+9VlnMyT5F3PaWIcGhzQKIzxdB95nhslaCQv3c63M7LBnvzVo1J9SUN85RMbP/57bP1HvhhU1nvL8=|IQPtf8v7k83MFZEhazSYXSdu98BBU5rqtvC4keVWyHM=", PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Ww2chogqCpaAR7Uw448am4b7vDFXiM5kXjFlGfXBlrAdAqTTggEvTDlMNYqPlCo+mBM6iFmTTUY9rpZBvFskMnKvsvpJ47/fehAH2o2e3Ulv/5NFevaVCMCmpkBDtbMbO1A4a3btdRtCP8DsKWMefHauEpaoLxNTLWnOIZVfCMjsSgx2EvULHAZPTtbFwm4+UVKniM4ds4jvOsD85h4jn2aLs/jWJXFfxN8iVSqEqpC2TBvsPdyHb49xQoWWfF0Z6BiNqeNGKEU9Uos1pjL+kzhEzzSpH31PZT/ufJ/oo4+93wrUt57hb6f0jxiXhwd5yQ+9F6wVwpbfkq0IwhjOwIDAQAB",