Skip to content

Commit 44a82d3

Browse files
authored
[PM-22263] Integate Rust SDK to Seeder (#6150)
Adds a Rust SDK for performing seed related cryptograhic operations. It depends on internal portions of our Rust SDK. Primarily parts of the bitwarden-crypto crate.
1 parent 9c51c99 commit 44a82d3

17 files changed

Lines changed: 3651 additions & 0 deletions

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ src/Admin/Views/Tools @bitwarden/team-billing-dev
9696
# The PushType enum is expected to be editted by anyone without need for Platform review
9797
src/Core/Platform/Push/PushType.cs
9898

99+
# SDK
100+
util/RustSdk @bitwarden/team-sdk-sme
101+
99102
# Multiple owners - DO NOT REMOVE (BRE)
100103
**/packages.lock.json
101104
Directory.Build.props

.github/renovate.json5

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22
$schema: "https://docs.renovatebot.com/renovate-schema.json",
33
extends: ["github>bitwarden/renovate-config"], // Extends our default configuration for pinned dependencies
44
enabledManagers: [
5+
"cargo",
56
"dockerfile",
67
"docker-compose",
78
"github-actions",
89
"npm",
910
"nuget",
1011
],
1112
packageRules: [
13+
{
14+
groupName: "cargo minor",
15+
matchManagers: ["cargo"],
16+
matchUpdateTypes: ["minor"],
17+
},
1218
{
1319
groupName: "dockerfile minor",
1420
matchManagers: ["dockerfile"],

.github/workflows/test.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ jobs:
3232
- name: Set up .NET
3333
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
3434

35+
- name: Install rust
36+
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # stable
37+
with:
38+
toolchain: stable
39+
40+
- name: Cache cargo registry
41+
uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7
42+
3543
- name: Print environment
3644
run: |
3745
dotnet --info

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ bitwarden_license/src/Sso/wwwroot/assets
216216
.mono
217217
src/Core/MailTemplates/Mjml/out
218218
src/Core/MailTemplates/Mjml/out-hbs
219+
NativeMethods.g.cs
220+
util/RustSdk/rust/target
219221

220222
src/Admin/Admin.zip
221223
src/Api/Api.zip

bitwarden-server.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Seeder", "util\Seeder\Seede
133133
EndProject
134134
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbSeederUtility", "util\DbSeederUtility\DbSeederUtility.csproj", "{17A89266-260A-4A03-81AE-C0468C6EE06E}"
135135
EndProject
136+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RustSdk", "util\RustSdk\RustSdk.csproj", "{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7}"
136137
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedWeb.Test", "test\SharedWeb.Test\SharedWeb.Test.csproj", "{AD59537D-5259-4B7A-948F-0CF58E80B359}"
137138
EndProject
138139
Global
@@ -339,6 +340,10 @@ Global
339340
{17A89266-260A-4A03-81AE-C0468C6EE06E}.Debug|Any CPU.Build.0 = Debug|Any CPU
340341
{17A89266-260A-4A03-81AE-C0468C6EE06E}.Release|Any CPU.ActiveCfg = Release|Any CPU
341342
{17A89266-260A-4A03-81AE-C0468C6EE06E}.Release|Any CPU.Build.0 = Release|Any CPU
343+
{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
344+
{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
345+
{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
346+
{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7}.Release|Any CPU.Build.0 = Release|Any CPU
342347
{AD59537D-5259-4B7A-948F-0CF58E80B359}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
343348
{AD59537D-5259-4B7A-948F-0CF58E80B359}.Debug|Any CPU.Build.0 = Debug|Any CPU
344349
{AD59537D-5259-4B7A-948F-0CF58E80B359}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -397,6 +402,7 @@ Global
397402
{3631BA42-6731-4118-A917-DAA43C5032B9} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
398403
{9A612EBA-1C0E-42B8-982B-62F0EE81000A} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
399404
{17A89266-260A-4A03-81AE-C0468C6EE06E} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
405+
{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
400406
{AD59537D-5259-4B7A-948F-0CF58E80B359} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
401407
EndGlobalSection
402408
GlobalSection(ExtensibilityGlobals) = postSolution

util/RustSdk/NativeMethods.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Reflection;
2+
using System.Runtime.InteropServices;
3+
4+
namespace Bit.RustSDK;
5+
6+
public static partial class NativeMethods
7+
{
8+
// https://docs.microsoft.com/en-us/dotnet/standard/native-interop/cross-platform
9+
// Library path will search
10+
// win => __DllName, __DllName.dll
11+
// linux, osx => __DllName.so, __DllName.dylib
12+
13+
static NativeMethods()
14+
{
15+
NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, DllImportResolver);
16+
}
17+
18+
static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
19+
{
20+
if (libraryName != __DllName) return IntPtr.Zero;
21+
22+
var path = "runtimes/";
23+
var extension = "";
24+
25+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
26+
{
27+
path += "win-";
28+
extension = ".dll";
29+
}
30+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
31+
{
32+
path += "osx-";
33+
extension = ".dylib";
34+
}
35+
else
36+
{
37+
path += "linux-";
38+
extension = ".so";
39+
}
40+
41+
if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
42+
{
43+
path += "x86";
44+
}
45+
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
46+
{
47+
path += "x64";
48+
}
49+
else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
50+
{
51+
path += "arm64";
52+
}
53+
54+
path += "/native/" + __DllName + extension;
55+
56+
return NativeLibrary.Load(Path.Combine(AppContext.BaseDirectory, path), assembly, searchPath);
57+
}
58+
}

util/RustSdk/RustSdk.csproj

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<RootNamespace>Bit.RustSDK</RootNamespace>
8+
<UserSecretsId>Bit.RustSDK</UserSecretsId>
9+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Content Include="rust/target/release/libsdk*.dylib">
14+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
15+
<PackageCopyToOutput>true</PackageCopyToOutput>
16+
<Link>runtimes/osx-arm64/native/libsdk.dylib</Link>
17+
</Content>
18+
<Content Include="./rust/target/release/libsdk*.so">
19+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
20+
<PackageCopyToOutput>true</PackageCopyToOutput>
21+
<Link>runtimes/linux-x64/native/libsdk.dylib</Link>
22+
</Content>
23+
<Content Include="./rust/target/release/libsdk*.dll">
24+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
25+
<PackageCopyToOutput>true</PackageCopyToOutput>
26+
<Link>runtimes/windows-x64/native/libsdk.dylib</Link>
27+
</Content>
28+
29+
<!-- This is a work around because this file is compiled by the PreBuild event below, and won't
30+
always be detected -->
31+
<Compile Remove="NativeMethods.g.cs" />
32+
</ItemGroup>
33+
34+
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
35+
<Exec Command="cargo build --release" WorkingDirectory="$(ProjectDir)/rust" />
36+
<ItemGroup>
37+
<Compile Include="NativeMethods.g.cs" />
38+
</ItemGroup>
39+
</Target>
40+
41+
</Project>

util/RustSdk/RustSdkException.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Bit.RustSDK;
2+
3+
/// <summary>
4+
/// Exception thrown when the Rust SDK operations fail
5+
/// </summary>
6+
public class RustSdkException : Exception
7+
{
8+
public RustSdkException() : base("An error occurred in the Rust SDK operation")
9+
{
10+
}
11+
12+
public RustSdkException(string message) : base(message)
13+
{
14+
}
15+
16+
public RustSdkException(string message, Exception innerException) : base(message, innerException)
17+
{
18+
}
19+
}

util/RustSdk/RustSdkService.cs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System.Runtime.InteropServices;
2+
using System.Text;
3+
using System.Text.Json;
4+
5+
namespace Bit.RustSDK;
6+
7+
public class UserKeys
8+
{
9+
public required string MasterPasswordHash { get; set; }
10+
/// <summary>
11+
/// Base64 encoded UserKey
12+
/// </summary>
13+
public required string Key { get; set; }
14+
public required string EncryptedUserKey { get; set; }
15+
public required string PublicKey { get; set; }
16+
public required string PrivateKey { get; set; }
17+
}
18+
19+
public class OrganizationKeys
20+
{
21+
/// <summary>
22+
/// Base64 encoded SymmetricCryptoKey
23+
/// </summary>
24+
public required string Key { get; set; }
25+
26+
public required string PublicKey { get; set; }
27+
public required string PrivateKey { get; set; }
28+
}
29+
30+
/// <summary>
31+
/// Service implementation that provides a C# friendly interface to the Rust SDK
32+
/// </summary>
33+
public class RustSdkService
34+
{
35+
private static readonly JsonSerializerOptions CaseInsensitiveOptions = new()
36+
{
37+
PropertyNameCaseInsensitive = true
38+
};
39+
40+
public unsafe UserKeys GenerateUserKeys(string email, string password)
41+
{
42+
var emailBytes = StringToRustString(email);
43+
var passwordBytes = StringToRustString(password);
44+
45+
fixed (byte* emailPtr = emailBytes)
46+
fixed (byte* passwordPtr = passwordBytes)
47+
{
48+
var resultPtr = NativeMethods.generate_user_keys(emailPtr, passwordPtr);
49+
50+
var result = TakeAndDestroyRustString(resultPtr);
51+
52+
return JsonSerializer.Deserialize<UserKeys>(result, CaseInsensitiveOptions)!;
53+
}
54+
}
55+
56+
public unsafe OrganizationKeys GenerateOrganizationKeys()
57+
{
58+
var resultPtr = NativeMethods.generate_organization_keys();
59+
60+
var result = TakeAndDestroyRustString(resultPtr);
61+
62+
return JsonSerializer.Deserialize<OrganizationKeys>(result, CaseInsensitiveOptions)!;
63+
}
64+
65+
public unsafe string GenerateUserOrganizationKey(string userKey, string orgKey)
66+
{
67+
var userKeyBytes = StringToRustString(userKey);
68+
var orgKeyBytes = StringToRustString(orgKey);
69+
70+
fixed (byte* userKeyPtr = userKeyBytes)
71+
fixed (byte* orgKeyPtr = orgKeyBytes)
72+
{
73+
var resultPtr = NativeMethods.generate_user_organization_key(userKeyPtr, orgKeyPtr);
74+
75+
var result = TakeAndDestroyRustString(resultPtr);
76+
77+
return result;
78+
}
79+
}
80+
81+
82+
private static byte[] StringToRustString(string str)
83+
{
84+
return Encoding.UTF8.GetBytes(str + '\0');
85+
}
86+
87+
private static unsafe string TakeAndDestroyRustString(byte* ptr)
88+
{
89+
if (ptr == null)
90+
{
91+
throw new RustSdkException("Pointer is null");
92+
}
93+
94+
var result = Marshal.PtrToStringUTF8((IntPtr)ptr);
95+
NativeMethods.free_c_string(ptr);
96+
97+
if (result == null)
98+
{
99+
throw new RustSdkException("Failed to convert native result to string");
100+
}
101+
102+
return result;
103+
}
104+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace Bit.RustSDK;
2+
3+
/// <summary>
4+
/// Factory for creating Rust SDK service instances
5+
/// </summary>
6+
public static class RustSdkServiceFactory
7+
{
8+
/// <summary>
9+
/// Creates a singleton instance of the Rust SDK service (thread-safe)
10+
/// </summary>
11+
/// <returns>A singleton IRustSdkService instance</returns>
12+
public static RustSdkService CreateSingleton()
13+
{
14+
return SingletonHolder.Instance;
15+
}
16+
17+
private static class SingletonHolder
18+
{
19+
internal static readonly RustSdkService Instance = new();
20+
}
21+
}

0 commit comments

Comments
 (0)