Compare commits

..

10 Commits

Author SHA1 Message Date
Genva
bdc258da7c Merge ae20e6da9d into 676dd7b85c 2025-06-16 20:35:38 +02:00
Genva
ae20e6da9d Revert "Remove unnecessary changes in LicensingService.cs"
This reverts commit d8465e1aec.
2025-06-16 20:34:14 +02:00
Genva
d8465e1aec Remove unnecessary changes in LicensingService.cs 2025-06-16 16:37:32 +02:00
Genva
a3a6564776 fix executable parameter 2025-06-16 16:34:12 +02:00
Genva
eaca0f047e remove loose file parameter 2025-06-16 14:29:49 +02:00
Genva
3f5910c616 pass single file application to license gen 2025-06-16 14:29:22 +02:00
Genva
936059785a support loading Core.dll from single file application 2025-06-16 14:23:04 +02:00
Genva
5b5d1301c9 remove obsolete project 2025-06-16 10:21:24 +02:00
Genva
317af2994b Clone only current version tag 2025-06-16 10:16:27 +02:00
Genva
1a2073cf35 Build image from source 2025-06-14 15:46:18 +02:00
9 changed files with 27 additions and 432 deletions

View File

@@ -13,12 +13,3 @@ jobs:
- run: - run:
name: Build script name: Build script
command: ./build.sh command: ./build.sh
- run:
name: Build licenseGen
command: ./src/licenseGen/build.sh
- run:
name: Test generating user license
command: ./src/licenseGen/run.sh ./.keys/cert.pfx user TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23
- run:
name: Test generating organization license
command: ./src/licenseGen/run.sh ./.keys/cert.pfx org TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23

View File

@@ -4,8 +4,6 @@ BitBetter is is a tool to modify Bitwarden's core dll to allow you to generate y
Please see the FAQ below for details on why this software was created. Please see the FAQ below for details on why this software was created.
Looking for a solution to the Lite (formerly unified) version of bitwarden, [go to the Lite branch](../../tree/lite).
_Beware! BitBetter does janky stuff to rewrite the bitwarden core dll and allow the installation of a self signed certificate. Use at your own risk!_ _Beware! BitBetter does janky stuff to rewrite the bitwarden core dll and allow the installation of a self signed certificate. Use at your own risk!_
Credit to https://github.com/h44z/BitBetter and https://github.com/jakeswenson/BitBetter Credit to https://github.com/h44z/BitBetter and https://github.com/jakeswenson/BitBetter
@@ -68,27 +66,15 @@ From the BitBetter directory, simply run:
This will create a new self-signed certificate in the `.keys` directory if one does not already exist and then create a modified version of the official `bitwarden/api` called `bitbetter/api` and a modified version of the `bitwarden/identity` called `bitbetter/identity`. This will create a new self-signed certificate in the `.keys` directory if one does not already exist and then create a modified version of the official `bitwarden/api` called `bitbetter/api` and a modified version of the `bitwarden/identity` called `bitbetter/identity`.
By default, the build script runs in **fast patch mode**: it pulls the official Bitwarden images from the GitHub Container Registry and patches the license certificate directly inside them — no local compilation required.
To instead **build from source**, set the `BITBETTER_BUILD_FROM_SOURCE` environment variable to `1` before running the script:
```bash
BITBETTER_BUILD_FROM_SOURCE=1 ./build.sh
```
In source build mode the script clones the Bitwarden server repository at the matching version tag, replaces the embedded license certificate, and compiles the `api` and `identity` images from scratch. This takes considerably longer but gives you full control over the build.
You may now simply create the file `/path/to/bwdata/docker/docker-compose.override.yml` with the following contents to utilize the modified images. You may now simply create the file `/path/to/bwdata/docker/docker-compose.override.yml` with the following contents to utilize the modified images.
```yaml ```yaml
services: services:
api: api:
image: bitbetter/api image: bitbetter/api
pull_policy: never
identity: identity:
image: bitbetter/identity image: bitbetter/identity
pull_policy: never
``` ```
You'll also want to edit the `/path/to/bwdata/scripts/run.sh` file. In the `function restart()` block, comment out the call to `dockerComposePull`. You'll also want to edit the `/path/to/bwdata/scripts/run.sh` file. In the `function restart()` block, comment out the call to `dockerComposePull`.
@@ -103,16 +89,6 @@ You can now start or restart Bitwarden as normal and the modified api will be us
To update Bitwarden, the provided `update-bitwarden.sh` script can be used. It will rebuild the BitBetter images and automatically update Bitwarden afterwards. Docker pull errors can be ignored for api and identity images. To update Bitwarden, the provided `update-bitwarden.sh` script can be used. It will rebuild the BitBetter images and automatically update Bitwarden afterwards. Docker pull errors can be ignored for api and identity images.
By default, the images are built in **fast patch mode**: the official Bitwarden images are pulled from the GitHub Container Registry and the license certificate are directly patched inside them.
To instead **build from source**, set the `BITBETTER_BUILD_FROM_SOURCE` environment variable to `1` before running the script:
```bash
BITBETTER_BUILD_FROM_SOURCE=1 ./update-bitwarden.sh
```
In source build mode the script clones the Bitwarden server repository at the matching version tag, replaces the embedded license certificate, and compiles the `api` and `identity` images from scratch. This takes considerably longer but gives you full control over the build.
You can either run this script without providing any parameters in interactive mode (`./update-bitwarden.sh`) or by setting the parameters as follows, to run the script in non-interactive mode: You can either run this script without providing any parameters in interactive mode (`./update-bitwarden.sh`) or by setting the parameters as follows, to run the script in non-interactive mode:
```bash ```bash
./update-bitwarden.sh param1 param2 param3 ./update-bitwarden.sh param1 param2 param3

View File

@@ -6,88 +6,23 @@ BW_VERSION=$(curl -sL https://go.btwrdn.co/bw-sh-versions | grep '^ *"'coreVersi
echo "Building BitBetter for BitWarden version $BW_VERSION" echo "Building BitBetter for BitWarden version $BW_VERSION"
# Enable BuildKit for better build experience and to ensure platform args are populated
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1
# Determine host architecture to use as default BUILDPLATFORM / TARGETPLATFORM if not supplied.
# Allow override via environment variables when invoking the script.
HOST_UNAME_ARCH=$(uname -m 2>/dev/null || echo unknown)
case "$HOST_UNAME_ARCH" in
x86_64|amd64) DEFAULT_ARCH=amd64 ;;
aarch64|arm64) DEFAULT_ARCH=arm64 ;;
armv7l|armv7) DEFAULT_ARCH=arm/v7 ;;
*) DEFAULT_ARCH=amd64 ;;
esac
: "${BUILDPLATFORM:=linux/${DEFAULT_ARCH}}"
: "${TARGETPLATFORM:=linux/${DEFAULT_ARCH}}"
echo "Using BUILDPLATFORM=$BUILDPLATFORM TARGETPLATFORM=$TARGETPLATFORM"
# If there aren't any keys, generate them first. # If there aren't any keys, generate them first.
[ -e "$DIR/.keys/cert.cert" ] || "$DIR/.keys/generate-keys.sh" [ -e "$DIR/.keys/cert.cert" ] || "$DIR/.keys/generate-keys.sh"
if [ "${BITBETTER_BUILD_FROM_SOURCE:-0}" = "1" ]; then
echo "--- Source build mode ---"
# Prepare Bitwarden server repository # Prepare Bitwarden server repository
rm -rf $DIR/server rm -rf $DIR/server
git clone --branch "v${BW_VERSION}" --depth 1 https://github.com/bitwarden/server.git $DIR/server git clone --branch "v${BW_VERSION}" --depth 1 https://github.com/bitwarden/server.git $DIR/server
# Replace certificate file and thumbprint # Optional, replacing the thumbprint in code has no actual effect
old_thumbprint=$(openssl x509 -inform DER -fingerprint -noout -in $DIR/server/src/Core/licensing.cer | cut -d= -f2 | tr -d ':') old_thumbprint=$(openssl x509 -fingerprint -noout -in $DIR/server/src/Core/licensing.cer | cut -d= -f2 | tr -d ':')
new_thumbprint=$(openssl x509 -inform DER -fingerprint -noout -in $DIR/.keys/cert.cert | cut -d= -f2 | tr -d ':') new_thumbprint=$(openssl x509 -fingerprint -noout -in $DIR/.keys/cert.cert | cut -d= -f2 | tr -d ':')
sed -i -e "s/$old_thumbprint/$new_thumbprint/g" $DIR/server/src/Core/Billing/Services/Implementations/LicensingService.cs sed -i -e "s/$old_thumbprint/$new_thumbprint/g" $DIR/server/src/Core/Services/Implementations/LicensingService.cs
# Replace certificate file
cp $DIR/.keys/cert.cert $DIR/server/src/Core/licensing.cer cp $DIR/.keys/cert.cert $DIR/server/src/Core/licensing.cer
docker build \ docker build --no-cache --label com.bitwarden.product="bitbetter" $DIR/server -f $DIR/server/src/Api/Dockerfile -t bitbetter/api
--no-cache \ docker build --no-cache --label com.bitwarden.product="bitbetter" $DIR/server -f $DIR/server/src/Identity/Dockerfile -t bitbetter/identity
--platform "$TARGETPLATFORM" \
--build-arg BUILDPLATFORM="$BUILDPLATFORM" \
--build-arg TARGETPLATFORM="$TARGETPLATFORM" \
--label com.bitwarden.product="bitbetter" \
-f $DIR/server/src/Api/Dockerfile \
-t bitbetter/api \
$DIR/server
docker build \
--no-cache \
--platform "$TARGETPLATFORM" \
--build-arg BUILDPLATFORM="$BUILDPLATFORM" \
--build-arg TARGETPLATFORM="$TARGETPLATFORM" \
--label com.bitwarden.product="bitbetter" \
-f $DIR/server/src/Identity/Dockerfile \
-t bitbetter/identity \
$DIR/server
else
echo "--- Fast patch mode ---"
mkdir -p "$DIR/src/bitBetter/.keys"
cp "$DIR/.keys/cert.cert" "$DIR/src/bitBetter/.keys/cert.cert"
# Build the patcher tool inside the SDK container
docker run --rm \
-v "$DIR/src/bitBetter:/bitBetter" \
-w /bitBetter \
mcr.microsoft.com/dotnet/sdk:8.0 sh build.sh
docker build \
--no-cache \
--platform "$TARGETPLATFORM" \
--label com.bitwarden.product="bitbetter" \
--build-arg BITWARDEN_TAG="ghcr.io/bitwarden/api:$BW_VERSION" \
-t bitbetter/api \
"$DIR/src/bitBetter"
docker build \
--no-cache \
--platform "$TARGETPLATFORM" \
--label com.bitwarden.product="bitbetter" \
--build-arg BITWARDEN_TAG="ghcr.io/bitwarden/identity:$BW_VERSION" \
-t bitbetter/identity \
"$DIR/src/bitBetter"
fi
docker tag bitbetter/api bitbetter/api:latest docker tag bitbetter/api bitbetter/api:latest
docker tag bitbetter/identity bitbetter/identity:latest docker tag bitbetter/identity bitbetter/identity:latest
@@ -95,5 +30,5 @@ docker tag bitbetter/api bitbetter/api:$BW_VERSION
docker tag bitbetter/identity bitbetter/identity:$BW_VERSION docker tag bitbetter/identity bitbetter/identity:$BW_VERSION
# Remove old instances of the image after a successful build. # Remove old instances of the image after a successful build.
ids=$( docker image ls --format="{{ .ID }} {{ .Tag }}" 'bitbetter/*' | grep -E -v -- "CREATED|latest|${BW_VERSION}" | awk '{ ids = (ids ? ids FS $1 : $1) } END { print ids }' ) ids=$( docker images bitbetter/* | grep -E -v -- "CREATED|latest|${BW_VERSION}" | awk '{ print $3 }' )
[ -n "$ids" ] && docker rmi $ids || true [ -n "$ids" ] && docker rmi $ids || true

View File

@@ -1,13 +0,0 @@
ARG BITWARDEN_TAG
FROM ${BITWARDEN_TAG}
COPY bin/Release/net8.0/publish/* /bitBetter/
COPY ./.keys/cert.cert /newLicensing.cer
RUN set -e; set -x; \
if [ -f /app/Api ]; then EXEC=Api; else EXEC=Identity; fi && \
dotnet /bitBetter/bitBetter.dll /newLicensing.cer /app/$EXEC && \
rm -f /app/$EXEC && \
printf '#!/bin/sh\nexec dotnet /app/%s.dll "$@"\n' "$EXEC" > /app/$EXEC && \
chmod +x /app/$EXEC && \
rm -rf /bitBetter /newLicensing.cer

View File

@@ -1,147 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json.Nodes;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using SingleFileExtractor.Core;
namespace BitwardenSelfLicensor
{
class Program
{
static int Main(string[] args)
{
string cerFile = args.Length >= 1 ? args[0] : "/newLicensing.cer";
string inputPath = args.Length >= 2 ? args[1] : "/app/Api";
string coreDllPath;
string extractDir = Path.GetDirectoryName(Path.GetFullPath(inputPath));
if (string.Equals(Path.GetExtension(inputPath), ".dll", StringComparison.OrdinalIgnoreCase))
{
// Input is already a direct Core.dll path — skip bundle extraction
coreDllPath = inputPath;
}
else
{
try
{
var reader = new ExecutableReader(inputPath);
reader.ExtractToDirectory(extractDir);
Console.WriteLine($"Extracted bundle to {extractDir}");
coreDllPath = Path.Combine(extractDir, "Core.dll");
// The extracted runtimeconfig.json is in self-contained format (no "framework" key).
// Running "dotnet App.dll" requires framework-dependent format; without it .NET looks
// for libhostpolicy.so in the app dir (which isn't there) and crashes.
FixRuntimeConfig(extractDir, Path.GetFileNameWithoutExtension(inputPath));
}
catch (Exception ex)
{
Console.Error.WriteLine($"ERROR: Failed to extract single-file bundle '{inputPath}': {ex.Message}");
return 1;
}
}
Console.WriteLine($"Patching: {coreDllPath}");
var certBytes = File.ReadAllBytes(cerFile);
var module = ModuleDefinition.ReadModule(new MemoryStream(File.ReadAllBytes(coreDllPath)));
// Replace embedded certificate resource
var existingRes = module.Resources
.OfType<EmbeddedResource>()
.FirstOrDefault(r => r.Name == "Bit.Core.licensing.cer");
if (existingRes == null)
{
Console.Error.WriteLine("ERROR: Embedded resource 'Bit.Core.licensing.cer' not found in Core.dll");
return 1;
}
Console.WriteLine($"Found resource: {existingRes.Name}");
module.Resources.Add(new EmbeddedResource("Bit.Core.licensing.cer", existingRes.Attributes, certBytes));
module.Resources.Remove(existingRes);
var existingCert = new X509Certificate2(existingRes.GetResourceData());
var newCert = new X509Certificate2(certBytes);
Console.WriteLine($"Old thumbprint: {existingCert.Thumbprint}");
Console.WriteLine($"New thumbprint: {newCert.Thumbprint}");
// Find LicensingService by class name (namespace-agnostic to handle renames)
var type = module.Types.FirstOrDefault(t => t.Name == "LicensingService");
if (type == null)
{
Console.Error.WriteLine("ERROR: LicensingService not found in Core.dll");
return 1;
}
Console.WriteLine($"Found: {type.FullName}");
var ctor = type.Resolve().GetConstructors().First();
var rewriter = ctor.Body.GetILProcessor();
// Use Contains() to handle the hidden Unicode LRM character (\u200E) that Bitwarden
// prepends to the production thumbprint string literal in LicensingService.cs
var instToReplace = ctor.Body.Instructions
.Where(i => i.OpCode == OpCodes.Ldstr)
.FirstOrDefault(i => ((string)i.Operand)
.Contains(existingCert.Thumbprint, StringComparison.OrdinalIgnoreCase));
if (instToReplace != null)
{
Console.WriteLine($"Replacing thumbprint Ldstr: '{instToReplace.Operand}'");
rewriter.Replace(instToReplace, Instruction.Create(OpCodes.Ldstr, newCert.Thumbprint));
}
else
{
Console.WriteLine("WARNING: Thumbprint Ldstr not found — cert resource replaced anyway");
}
module.Write(coreDllPath);
Console.WriteLine("Done.");
return 0;
}
// Converts a self-contained runtimeconfig.json to framework-dependent so the app can be
// launched with "dotnet App.dll" using the system-installed ASP.NET Core runtime.
static void FixRuntimeConfig(string dir, string appName)
{
var path = Path.Combine(dir, $"{appName}.runtimeconfig.json");
if (!File.Exists(path))
{
Console.WriteLine($"runtimeconfig not found at {path}, skipping");
return;
}
var root = JsonNode.Parse(File.ReadAllText(path))!;
var opts = root["runtimeOptions"]!.AsObject();
// Derive framework name/version from the self-contained includedFrameworks before removing it
string fwName = "Microsoft.AspNetCore.App";
string fwVersion = "8.0.0";
if (opts["includedFrameworks"] is JsonArray included && included.Count > 0)
{
var first = included[0]!.AsObject();
fwName = first["name"]?.GetValue<string>() ?? fwName;
fwVersion = first["version"]?.GetValue<string>() ?? fwVersion;
}
// Remove self-contained markers
opts.Remove("includedFrameworks");
// Add framework-dependent reference (LatestPatch allows compatibility across patch releases only)
opts["framework"] = new JsonObject
{
["name"] = fwName,
["version"] = fwVersion
};
opts["rollForward"] = "LatestPatch";
File.WriteAllText(path, root.ToJsonString(new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
Console.WriteLine($"Fixed runtimeconfig: {path}");
}
}
}

View File

@@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
<PackageReference Include="SingleFileExtractor.Core" Version="2.3.0" />
</ItemGroup>
</Project>

View File

@@ -1,7 +0,0 @@
#!/bin/bash
set -e
set -x
dotnet restore
dotnet publish -c Release -o bin/Release/net8.0/publish

View File

@@ -1,15 +1,11 @@
namespace BitwardenSelfLicensor namespace BitwardenSelfLicensor
{ {
using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.CommandLineUtils;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json; using Newtonsoft.Json;
using SingleFileExtractor.Core; using SingleFileExtractor.Core;
using System; using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO; using System.IO;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
public static class Program public static class Program
@@ -354,7 +350,7 @@ namespace BitwardenSelfLicensor
{ {
var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath);
var type = core.GetType("Bit.Core.Billing.Models.Business.UserLicense"); var type = core.GetType("Bit.Core.Models.Business.UserLicense");
var licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); var licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType");
var license = Activator.CreateInstance(type); var license = Activator.CreateInstance(type);
@@ -364,23 +360,19 @@ namespace BitwardenSelfLicensor
type.GetProperty(name).SetValue(license, value); type.GetProperty(name).SetValue(license, value);
} }
var licenseKey = string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key; set("LicenseKey", string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
set("LicenseKey", licenseKey);
set("Id", userId); set("Id", userId);
set("Name", userName); set("Name", userName);
set("Email", email); set("Email", email);
set("Premium", true); set("Premium", true);
set("MaxStorageGb", storage == 0 ? short.MaxValue : storage); set("MaxStorageGb", storage == 0 ? short.MaxValue : storage);
set("Version", 1); set("Version", 1);
var now = DateTime.UtcNow; set("Issued", DateTime.UtcNow);
set("Issued", now); set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
set("Refresh", now.AddYears(100).AddMonths(-1)); set("Expires", DateTime.UtcNow.AddYears(100));
set("Expires", now.AddYears(100));
set("Trial", false); set("Trial", false);
set("LicenseType", Enum.Parse(licenseTypeEnum, "User")); set("LicenseType", Enum.Parse(licenseTypeEnum, "User"));
set("Token", GenerateUserToken(cert, userId, licenseKey, userName, email, storage, now));
set("Hash", Convert.ToBase64String((byte[])type.GetMethod("ComputeHash").Invoke(license, new object[0]))); set("Hash", Convert.ToBase64String((byte[])type.GetMethod("ComputeHash").Invoke(license, new object[0])));
set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert }))); set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert })));
@@ -391,7 +383,7 @@ namespace BitwardenSelfLicensor
{ {
var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath);
var type = core.GetType("Bit.Core.Billing.Organizations.Models.OrganizationLicense"); var type = core.GetType("Bit.Core.Models.Business.OrganizationLicense");
var licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); var licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType");
var planTypeEnum = core.GetType("Bit.Core.Billing.Enums.PlanType"); var planTypeEnum = core.GetType("Bit.Core.Billing.Enums.PlanType");
@@ -402,15 +394,12 @@ namespace BitwardenSelfLicensor
type.GetProperty(name).SetValue(license, value); type.GetProperty(name).SetValue(license, value);
} }
var licenseKey = string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key; set("LicenseKey", string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
var businessNameFinal = string.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName;
set("LicenseKey", licenseKey);
set("InstallationId", instalId); set("InstallationId", instalId);
set("Id", Guid.NewGuid()); set("Id", Guid.NewGuid());
set("Name", userName); set("Name", userName);
set("BillingEmail", email); set("BillingEmail", email);
set("BusinessName", businessNameFinal); set("BusinessName", string.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName);
set("Enabled", true); set("Enabled", true);
set("Plan", "Enterprise (Annually)"); set("Plan", "Enterprise (Annually)");
set("PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually")); set("PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
@@ -435,134 +424,19 @@ namespace BitwardenSelfLicensor
set("UseSecretsManager", true); set("UseSecretsManager", true);
set("SmSeats", int.MaxValue); set("SmSeats", int.MaxValue);
set("SmServiceAccounts", int.MaxValue); set("SmServiceAccounts", int.MaxValue);
set("Version", 16); set("Version", 15); //This is set to 15 to use AllowAdminAccessToAllCollectionItems can be changed to 13 to just use Secrets Manager
var now = DateTime.UtcNow; set("Issued", DateTime.UtcNow);
set("Issued", now); set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
set("Refresh", now.AddYears(100).AddMonths(-1)); set("Expires", DateTime.UtcNow.AddYears(100));
set("Expires", now.AddYears(100));
set("ExpirationWithoutGracePeriod", now.AddYears(100));
set("Trial", false); set("Trial", false);
set("LicenseType", Enum.Parse(licenseTypeEnum, "Organization")); set("LicenseType", Enum.Parse(licenseTypeEnum, "Organization"));
set("LimitCollectionCreationDeletion", true); set("LimitCollectionCreationDeletion", true); //This will be used in the new version of BitWarden but can be applied now
set("AllowAdminAccessToAllCollectionItems", true); set("AllowAdminAccessToAllCollectionItems", true);
set("UseRiskInsights", true);
set("UseOrganizationDomains", true);
set("UseAdminSponsoredFamilies", true);
set("UseAutomaticUserConfirmation", true);
set("UsePhishingBlocker", true);
set("UseDisableSmAdsForUsers", true);
set("UseMyItems", true);
var orgId = (Guid)type.GetProperty("Id").GetValue(license);
set("Token", GenerateOrgToken(cert, orgId, instalId, licenseKey, email, businessNameFinal, userName, storage, planTypeEnum, now));
set("Hash", Convert.ToBase64String((byte[])type.GetMethod("ComputeHash").Invoke(license, new object[0]))); set("Hash", Convert.ToBase64String((byte[])type.GetMethod("ComputeHash").Invoke(license, new object[0])));
set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert }))); set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert })));
Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented));
} }
private static string GenerateUserToken(X509Certificate2 cert, Guid userId, string licenseKey, string name, string email, short maxStorageGb, DateTime now)
{
var secKey = new X509SecurityKey(cert);
var creds = new SigningCredentials(secKey, SecurityAlgorithms.RsaSha256);
var expires = now.AddYears(100);
var claims = new List<Claim>
{
new Claim("LicenseType", "User"),
new Claim("LicenseKey", licenseKey),
new Claim("Id", userId.ToString()),
new Claim("Name", name),
new Claim("Email", email),
new Claim("Premium", "true"),
new Claim("MaxStorageGb", (maxStorageGb == 0 ? short.MaxValue : maxStorageGb).ToString()),
new Claim("Trial", "false"),
new Claim("Issued", now.ToString("o")),
new Claim("Expires", expires.ToString("o")),
new Claim("Refresh", now.AddYears(100).AddMonths(-1).ToString("o")),
};
var handler = new JwtSecurityTokenHandler();
var token = new JwtSecurityToken(
issuer: "bitwarden",
audience: $"user:{userId}",
claims: claims,
notBefore: now,
expires: expires,
signingCredentials: creds);
return handler.WriteToken(token);
}
private static string GenerateOrgToken(X509Certificate2 cert, Guid orgId, Guid installationId, string licenseKey, string billingEmail, string businessName, string name, short maxStorageGb, Type planTypeEnum, DateTime now)
{
var secKey = new X509SecurityKey(cert);
var creds = new SigningCredentials(secKey, SecurityAlgorithms.RsaSha256);
var expires = now.AddYears(100);
// Resolve the integer value of EnterpriseAnnually from the runtime enum
var planTypeInt = Convert.ToInt32(Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
var claims = new List<Claim>
{
new Claim("LicenseType", "Organization"),
new Claim("LicenseKey", licenseKey),
new Claim("InstallationId", installationId.ToString()),
new Claim("Id", orgId.ToString()),
new Claim("Name", name),
new Claim("BillingEmail", billingEmail),
new Claim("BusinessName", businessName),
new Claim("Enabled", "true"),
new Claim("Plan", "Enterprise (Annually)"),
new Claim("PlanType", planTypeInt.ToString()),
new Claim("Seats", int.MaxValue.ToString()),
new Claim("MaxCollections", short.MaxValue.ToString()),
new Claim("MaxStorageGb", (maxStorageGb == 0 ? short.MaxValue : maxStorageGb).ToString()),
new Claim("SelfHost", "true"),
new Claim("UsersGetPremium", "true"),
new Claim("UseGroups", "true"),
new Claim("UseDirectory", "true"),
new Claim("UseEvents", "true"),
new Claim("UseTotp", "true"),
new Claim("Use2fa", "true"),
new Claim("UseApi", "true"),
new Claim("UsePolicies", "true"),
new Claim("UseSso", "true"),
new Claim("UseResetPassword", "true"),
new Claim("UseKeyConnector", "true"),
new Claim("UseScim", "true"),
new Claim("UseCustomPermissions", "true"),
new Claim("UsePasswordManager", "true"),
new Claim("UseSecretsManager", "true"),
new Claim("SmSeats", int.MaxValue.ToString()),
new Claim("SmServiceAccounts", int.MaxValue.ToString()),
new Claim("UseRiskInsights", "true"),
new Claim("UseAdminSponsoredFamilies", "true"),
new Claim("UseOrganizationDomains", "true"),
new Claim("UseAutomaticUserConfirmation", "true"),
new Claim("UseDisableSmAdsForUsers", "true"),
new Claim("UsePhishingBlocker", "true"),
new Claim("UseMyItems", "true"),
new Claim("LimitCollectionCreationDeletion", "true"),
new Claim("AllowAdminAccessToAllCollectionItems", "true"),
new Claim("Trial", "false"),
new Claim("Issued", now.ToString("o")),
new Claim("Expires", expires.ToString("o")),
new Claim("Refresh", now.AddYears(100).AddMonths(-1).ToString("o")),
new Claim("ExpirationWithoutGracePeriod", expires.ToString("o")),
};
var handler = new JwtSecurityTokenHandler();
var token = new JwtSecurityToken(
issuer: "bitwarden",
audience: $"organization:{orgId}",
claims: claims,
notBefore: now,
expires: expires,
signingCredentials: creds);
return handler.WriteToken(token);
}
} }
} }

View File

@@ -10,7 +10,6 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SingleFileExtractor.Core" Version="2.3.0" /> <PackageReference Include="SingleFileExtractor.Core" Version="2.3.0" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> <PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>