1
0
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:
Hinton
2025-07-31 10:20:53 +02:00
parent 072f9f2278
commit 75f11f68ac
7 changed files with 108 additions and 40 deletions

View File

@@ -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);
}
}

View File

@@ -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)
{
return NativeMethods.my_add(x, y);
}
catch (Exception ex)
var emailBytes = StringToRustString(email);
var passwordBytes = StringToRustString(password);
fixed (byte* emailPtr = emailBytes)
fixed (byte* passwordPtr = passwordBytes)
{
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)

View File

@@ -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",

View File

@@ -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"

View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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;
}