mirror of
https://github.com/bitwarden/server
synced 2025-12-13 06:43:45 +00:00
Generate valid keys using rust
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder.Recipes;
|
||||
using CommandDotNet;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Bit.DbSeederUtility;
|
||||
|
||||
@@ -26,14 +29,17 @@ public class Program
|
||||
// Create service provider with necessary services
|
||||
var services = new ServiceCollection();
|
||||
ServiceCollectionExtension.ConfigureServices(services);
|
||||
services.TryAddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Get a scoped DB context
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var scopedServices = scope.ServiceProvider;
|
||||
var db = scopedServices.GetRequiredService<DatabaseContext>();
|
||||
var passwordHasher = scopedServices.GetRequiredService<IPasswordHasher<User>>();
|
||||
|
||||
var recipe = new OrganizationWithUsersRecipe(db);
|
||||
var recipe = new OrganizationWithUsersRecipe(db, passwordHasher);
|
||||
recipe.Seed(name, users, domain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,40 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Bit.RustSDK;
|
||||
|
||||
public class UserKeys
|
||||
{
|
||||
public required string MasterPasswordHash { get; set; }
|
||||
public required string EncryptedUserKey { get; set; }
|
||||
public required string PublicKey { get; set; }
|
||||
public required string PrivateKey { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service implementation that provides a C# friendly interface to the Rust SDK
|
||||
/// </summary>
|
||||
public class RustSdkService
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds two integers using the native implementation
|
||||
/// </summary>
|
||||
/// <param name="x">First integer</param>
|
||||
/// <param name="y">Second integer</param>
|
||||
/// <returns>The sum of x and y</returns>
|
||||
public int Add(int x, int y)
|
||||
private static readonly JsonSerializerOptions CaseInsensitiveOptions = new()
|
||||
{
|
||||
try
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public unsafe UserKeys GenerateUserKeys(string email, string password)
|
||||
{
|
||||
var emailBytes = StringToRustString(email);
|
||||
var passwordBytes = StringToRustString(password);
|
||||
|
||||
fixed (byte* emailPtr = emailBytes)
|
||||
fixed (byte* passwordPtr = passwordBytes)
|
||||
{
|
||||
return NativeMethods.my_add(x, y);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new RustSdkException($"Failed to perform addition operation: {ex.Message}", ex);
|
||||
var resultPtr = NativeMethods.generate_user_keys(emailPtr, passwordPtr);
|
||||
|
||||
var result = TakeAndDestroyRustString(resultPtr);
|
||||
|
||||
return JsonSerializer.Deserialize<UserKeys>(result, CaseInsensitiveOptions)!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +50,8 @@ public class RustSdkService
|
||||
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');
|
||||
var emailBytes = StringToRustString(email);
|
||||
var passwordBytes = StringToRustString(password);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -63,6 +75,11 @@ public class RustSdkService
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] StringToRustString(string str)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(str + '\0');
|
||||
}
|
||||
|
||||
private static unsafe string TakeAndDestroyRustString(byte* ptr)
|
||||
{
|
||||
if (ptr == null)
|
||||
|
||||
14
util/RustSdk/rust/Cargo.lock
generated
14
util/RustSdk/rust/Cargo.lock
generated
@@ -184,7 +184,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"serde_repr",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.12",
|
||||
"uuid",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -220,7 +220,7 @@ dependencies = [
|
||||
"sha1",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.12",
|
||||
"typenum",
|
||||
"uuid",
|
||||
"zeroize",
|
||||
@@ -252,7 +252,7 @@ version = "1.0.0"
|
||||
source = "git+https://github.com/bitwarden/sdk-internal.git?rev=b0c950dad701bc419c76e8a7d37bf5c17a6909d6#b0c950dad701bc419c76e8a7d37bf5c17a6909d6"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1802,7 +1802,7 @@ dependencies = [
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1897,6 +1897,8 @@ dependencies = [
|
||||
"bitwarden-core",
|
||||
"bitwarden-crypto",
|
||||
"csbindgen",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1970,9 +1972,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
version = "1.0.141"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
|
||||
@@ -14,6 +14,8 @@ crate-type = ["cdylib"]
|
||||
[dependencies]
|
||||
bitwarden-core = { git = "https://github.com/bitwarden/sdk-internal.git", rev = "b0c950dad701bc419c76e8a7d37bf5c17a6909d6" }
|
||||
bitwarden-crypto = { git = "https://github.com/bitwarden/sdk-internal.git", rev = "b0c950dad701bc419c76e8a7d37bf5c17a6909d6" }
|
||||
serde = "=1.0.219"
|
||||
serde_json = "=1.0.141"
|
||||
|
||||
[build-dependencies]
|
||||
csbindgen = "=1.9.3"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
use std::{
|
||||
ffi::{c_char, CStr, CString},
|
||||
num::NonZeroU32,
|
||||
@@ -10,6 +11,39 @@ pub extern "C" fn my_add(x: i32, y: i32) -> i32 {
|
||||
x + y
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn generate_user_keys(
|
||||
email: *const c_char,
|
||||
password: *const c_char,
|
||||
) -> *const c_char {
|
||||
// TODO: We might want to make KDF configurable in the future.
|
||||
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 master_password_hash = master_key
|
||||
.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)
|
||||
.unwrap();
|
||||
let (user_key, encrypted_user_key) = master_key.make_user_key().unwrap();
|
||||
let keys = user_key.make_key_pair().unwrap();
|
||||
|
||||
let json = serde_json::json!({
|
||||
"masterPasswordHash": master_password_hash,
|
||||
"encryptedUserKey": encrypted_user_key.to_string(),
|
||||
"publicKey": keys.public.to_string(),
|
||||
"privateKey": keys.private.to_string(),
|
||||
})
|
||||
.to_string();
|
||||
|
||||
let result = CString::new(json).unwrap();
|
||||
|
||||
result.into_raw()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// The `email` and `password` pointers must be valid null-terminated C strings.
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.RustSDK;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Bit.Seeder.Factories;
|
||||
|
||||
public class UserSeeder
|
||||
{
|
||||
public static User CreateUser(string email)
|
||||
|
||||
public static User CreateUser(IPasswordHasher<User> passwordHasher, string email)
|
||||
{
|
||||
var nativeService = RustSdkServiceFactory.CreateSingleton();
|
||||
Console.WriteLine(NativeMethods.my_add(2, 3));
|
||||
var keys = nativeService.GenerateUserKeys(email, "asdfasdfasdf");
|
||||
|
||||
var password = nativeService.HashPassword(email, "asdfasdfasdf");
|
||||
|
||||
return new User
|
||||
var user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Email = email,
|
||||
MasterPassword = password,
|
||||
MasterPassword = null,
|
||||
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",
|
||||
PrivateKey = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=",
|
||||
Key = keys.EncryptedUserKey,
|
||||
PublicKey = keys.PublicKey,
|
||||
PrivateKey = keys.PrivateKey,
|
||||
ApiKey = "7gp59kKHt9kMlks0BuNC4IjNXYkljR",
|
||||
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = 600_000,
|
||||
};
|
||||
|
||||
user.MasterPassword = passwordHasher.HashPassword(user, keys.MasterPasswordHash);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder.Factories;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Bit.Seeder.Recipes;
|
||||
|
||||
public class OrganizationWithUsersRecipe(DatabaseContext db)
|
||||
public class OrganizationWithUsersRecipe(DatabaseContext db, IPasswordHasher<User> passwordHasher)
|
||||
{
|
||||
public Guid Seed(string name, int users, string domain)
|
||||
{
|
||||
var organization = OrganizationSeeder.CreateEnterprise(name, domain, users);
|
||||
var user = UserSeeder.CreateUser($"admin@{domain}");
|
||||
var user = UserSeeder.CreateUser(passwordHasher, $"admin@{domain}");
|
||||
var orgUser = organization.CreateOrganizationUser(user);
|
||||
|
||||
var additionalUsers = new List<User>();
|
||||
var additionalOrgUsers = new List<OrganizationUser>();
|
||||
for (var i = 0; i < users; i++)
|
||||
{
|
||||
var additionalUser = UserSeeder.CreateUser($"user{i}@{domain}");
|
||||
var additionalUser = UserSeeder.CreateUser(passwordHasher, $"user{i}@{domain}");
|
||||
additionalUsers.Add(additionalUser);
|
||||
additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser));
|
||||
}
|
||||
|
||||
db.Add(organization);
|
||||
//db.Add(organization);
|
||||
db.Add(user);
|
||||
db.Add(orgUser);
|
||||
//db.Add(orgUser);
|
||||
|
||||
db.SaveChanges();
|
||||
|
||||
// Use LinqToDB's BulkCopy for significant better performance
|
||||
db.BulkCopy(additionalUsers);
|
||||
db.BulkCopy(additionalOrgUsers);
|
||||
//db.BulkCopy(additionalUsers);
|
||||
//db.BulkCopy(additionalOrgUsers);
|
||||
|
||||
return organization.Id;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user