Fixes for newer containers (#280)

* Find the new file

Next extract Core.dll, patch, reinsert

* Prepare Linux script too

* Extract and patch Core.dll again

* Make build script dynamic

* Cleanup after ourselves

* Build, then delete file

* Attempt to deconstruct the new file

* Add missing quotes

* Add missing file

* Rectify dll location

* Fix dumb extra character matching

* Dynamically find LicensingService and improve error reporting

* Upgrade package

* Implement new code

* Filter out new lines

* Force the runtime config

* Use correct external .NET library

* Update to .NET 10 in build.sh

Copy .NET 10 runtime from aspnet10.0-alpine3.23 to the bitwarden-lite container

* Update to .NET 10 in build.ps1

Copy .NET 10 runtime from aspnet:10.0-alpine3.23 to bitwarden-lite container

* Update generateKeys.sh to OpenSSL 3.x-compatible

Update to OpenSSL 3.x-compatible cipher generation

* Update generateKeys.ps1 to OpenSSL 3.x-compatible

Update to OpenSSL 3.x-compatible cipher

* Update bitBetter Dockerfile to .NET 10

* Update bitBetter.csproj to .NET 10

* Switch to X509CertificateLoader, switch to patching multiple thumbprint certificates

Update Program.cs in bitBetter to switch from deprecated X509Certificate to X509CertificateLoader
Patch multiple thumbprint certificate instances

* Update licenseGen Dockerfile to .NET 10

* Update licenseGen.csproj to .NET 10

* Remove extra line

* Get rid of extra line

* Update deprecated X509Certificate2 in LicenseGen

* Cleanup

* Fix tabbing

---------

Co-authored-by: Michiel Hazelhof <m.hazelhof@fyn.nl>
Co-authored-by: huntb4646 <94577767+huntb4646@users.noreply.github.com>
This commit is contained in:
Michiel Hazelhof
2026-06-05 20:09:27 +02:00
committed by GitHub
parent d305a3f202
commit 287b6a4d34
11 changed files with 287 additions and 64 deletions

View File

@@ -92,7 +92,8 @@ New-item -ItemType Directory -Path $tempdirectory
# extract the files that need to be patched from the services that need to be patched into our temporary directory
foreach ($component in $components) {
New-item -itemtype Directory -path "$tempdirectory\$component"
docker cp $patchinstance`:/app/$component/Core.dll "$tempdirectory\$component\Core.dll"
docker cp $patchinstance`:/app/$component/$component "$tempdirectory\$component\$component"
docker cp $patchinstance`:/etc/supervisor.d/$($component.ToLower()).ini "$tempdirectory\$($component.ToLower()).ini"
}
# stop and remove our temporary container
@@ -103,12 +104,25 @@ docker rm bitwarden-extract
docker run -v "$tempdirectory`:/app/mount" --rm bitbetter/bitbetter
# create a new image with the patched files
docker build . --tag bitwarden-patched --file "$pwd\src\bitBetter\Dockerfile-bitwarden-patch"
if (Test-Path -Path "$pwd\Dockerfile-bitwarden-patch" -PathType Leaf) {
Remove-Item "$pwd\Dockerfile-bitwarden-patch" -Force
}
$dockerFile = "FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine3.23"
$dockerFile = -join($dockerFile, "FROM ghcr.io/bitwarden/lite:latest")
$dockerFile = -join($dockerFile, "COPY --from=0 /usr/share/dotnet /usr/share/dotnet")
foreach ($component in $components) {
$dockerFile = -join($dockerFile, "`n`nCOPY ./temp/$component/ /app/$component/")
$dockerFile = -join($dockerFile, "`nCOPY ./temp/$($component.ToLower()).ini /etc/supervisor.d/$($component.ToLower()).ini")
$dockerFile = -join($dockerFile, "`nRUN rm -f /app/$component/$component")
}
[System.IO.File]::WriteAllLines("$pwd\Dockerfile-bitwarden-patch", $dockerFile)
docker build . --tag bitwarden-patched --file "$pwd\Dockerfile-bitwarden-patch"
Remove-Item "$pwd\Dockerfile-bitwarden-patch" -Force
# start all user requested instances
if (Test-Path -Path "$pwd\.servers\serverlist.txt" -PathType Leaf) {
foreach($line in Get-Content "$pwd\.servers\serverlist.txt") {
if (!($line.StartsWith("#"))) {
if ((-not ($line.StartsWith("#"))) -and (-not [string]::IsNullOrWhiteSpace($line))) {
Invoke-Expression "& $line"
}
}

View File

@@ -93,7 +93,8 @@ mkdir $TEMPDIRECTORY
# extract the files that need to be patched from the services that need to be patched into our temporary directory
for COMPONENT in ${COMPONENTS[@]}; do
mkdir "$TEMPDIRECTORY/$COMPONENT"
docker cp $PATCHINSTANCE:/app/$COMPONENT/Core.dll "$TEMPDIRECTORY/$COMPONENT/Core.dll"
docker cp $PATCHINSTANCE:/app/$COMPONENT/$COMPONENT "$TEMPDIRECTORY/$COMPONENT/$COMPONENT"
docker cp $PATCHINSTANCE:/etc/supervisor.d/${COMPONENT,,}.ini "$TEMPDIRECTORY/${COMPONENT,,}.ini"
done
# stop and remove our temporary container
@@ -104,14 +105,27 @@ docker rm bitwarden-extract
docker run -v "$TEMPDIRECTORY:/app/mount" --rm bitbetter/bitbetter
# create a new image with the patched files
docker build . --tag bitwarden-patched --file "$PWD/src/bitBetter/Dockerfile-bitwarden-patch"
if [ -f "$PWD/Dockerfile-bitwarden-patch" ]; then
rm -f "$PWD/Dockerfile-bitwarden-patch"
fi
echo "FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine3.23" >> "$PWD/Dockerfile-bitwarden-patch"
echo "FROM ghcr.io/bitwarden/lite:latest" >> "$PWD/Dockerfile-bitwarden-patch"
echo "COPY --from=0 /usr/share/dotnet /usr/share/dotnet" >> "$PWD/Dockerfile-bitwarden-patch"
for COMPONENT in ${COMPONENTS[@]}; do
echo "" >> "$PWD/Dockerfile-bitwarden-patch"
echo "RUN rm -f /app/$COMPONENT/$COMPONENT" >> "$PWD/Dockerfile-bitwarden-patch"
echo "COPY ./temp/${COMPONENT,,}.ini /etc/supervisor.d/${COMPONENT,,}.ini" >> "$PWD/Dockerfile-bitwarden-patch"
echo "COPY ./temp/$COMPONENT/ /app/$COMPONENT/" >> "$PWD/Dockerfile-bitwarden-patch"
done
docker build . --tag bitwarden-patched --file "$PWD/Dockerfile-bitwarden-patch"
rm -f "$PWD/Dockerfile-bitwarden-patch"
# start all user requested instances
if [ -f "$PWD/.servers/serverlist.txt" ]; then
# convert line endings to unix
sed -i 's/\r$//' "$PWD/.servers/serverlist.txt"
cat "$PWD/.servers/serverlist.txt" | while read -r LINE; do
if [[ $LINE != "#"* ]]; then
if [[ $LINE != "#"* && -n $LINE ]]; then
bash -c "$LINE"
fi
done

View File

@@ -22,4 +22,4 @@ New-item -ItemType Directory -Path "$pwd\.keys"
# generate actual keys
Invoke-Expression "& '$opensslbinary' req -x509 -newkey rsa:4096 -keyout `"$pwd\.keys\key.pem`" -out `"$pwd\.keys\cert.cer`" -days 36500 -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -outform DER -passout pass:test"
Invoke-Expression "& '$opensslbinary' x509 -inform DER -in `"$pwd\.keys\cert.cer`" -out `"$pwd\.keys\cert.pem`""
Invoke-Expression "& '$opensslbinary' pkcs12 -export -out `"$pwd\.keys\cert.pfx`" -inkey `"$pwd\.keys\key.pem`" -in `"$pwd\.keys\cert.pem`" -passin pass:test -passout pass:test"
Invoke-Expression "& '$opensslbinary' pkcs12 -export -out `"$pwd\.keys\cert.pfx`" -inkey `"$pwd\.keys\key.pem`" -in `"$pwd\.keys\cert.pem`" -passin pass:test -passout pass:test -certpbe AES-256-CBC -keypbe AES-256-CBC -macalg SHA256"

View File

@@ -17,4 +17,4 @@ mkdir "$DIR"
# Generate new keys
openssl req -x509 -newkey rsa:4096 -keyout "$DIR/key.pem" -out "$DIR/cert.cer" -days 36500 -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -outform DER -passout pass:test
openssl x509 -inform DER -in "$DIR/cert.cer" -out "$DIR/cert.pem"
openssl pkcs12 -export -out "$DIR/cert.pfx" -inkey "$DIR/key.pem" -in "$DIR/cert.pem" -passin pass:test -passout pass:test
openssl pkcs12 -export -out "$DIR/cert.pfx" -inkey "$DIR/key.pem" -in "$DIR/cert.pem" -passin pass:test -passout pass:test -certpbe AES-256-CBC -keypbe AES-256-CBC -macalg SHA256

View File

@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /bitBetter
COPY . /bitBetter
@@ -7,7 +7,7 @@ COPY cert.cer /app/
RUN dotnet restore
RUN dotnet publish -c Release -o /app --no-restore
FROM mcr.microsoft.com/dotnet/sdk:8.0
FROM mcr.microsoft.com/dotnet/sdk:10.0
WORKDIR /app
COPY --from=build /app .

View File

@@ -1,3 +0,0 @@
FROM ghcr.io/bitwarden/lite:latest
COPY ./temp/ /app/

View File

@@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using dnlib.DotNet.Writer;
using dnlib.IO;
using SingleFileExtractor.Core;
namespace bitBetter;
@@ -15,12 +16,46 @@ internal class Program
private static Int32 Main()
{
const String certFile = "/app/cert.cer";
String[] files = Directory.GetFiles("/app/mount", "Core.dll", SearchOption.AllDirectories);
foreach (String file in files)
foreach (String iniFile in Directory.GetFiles("/app/mount/", "*.ini", SearchOption.TopDirectoryOnly))
{
Console.WriteLine(file);
ModuleDefMD moduleDefMd = ModuleDefMD.Load(file);
Console.WriteLine("Patching: " + iniFile);
String[] lines = File.ReadAllLines(iniFile);
for (Int32 i = 0; i < lines.Length; i++)
{
String line = lines[i];
if (!line.StartsWith("command=", StringComparison.Ordinal)) continue;
String appNameAndPath = line[(line.LastIndexOf('=') + 1)..];
lines[i] = "command=/usr/bin/dotnet \"" + appNameAndPath + ".dll\" --runtimeconfig \"" + appNameAndPath + ".runtimeconfig.json\"";
break;
}
File.WriteAllText(iniFile, String.Join("\n", lines), new UTF8Encoding(false));
}
foreach (String singleFile in Directory.GetFiles("/app/mount/", "*", SearchOption.AllDirectories))
{
if (Path.HasExtension(singleFile)) continue;
Console.WriteLine("Extracting: " + singleFile);
ExecutableReader reader1 = new(singleFile);
String currentDirectory = Path.GetDirectoryName(singleFile);
String newCoreDll = Path.Combine(currentDirectory, "Core.dll");
reader1.ExtractToDirectory(currentDirectory);
reader1.Dispose();
File.Delete(singleFile);
if (!File.Exists(newCoreDll))
{
Console.WriteLine("Could not extract Core.dll for " + singleFile);
Environment.Exit(-1);
}
Console.WriteLine("Extracted: " + newCoreDll);
ModuleDefMD moduleDefMd = ModuleDefMD.Load(newCoreDll);
Byte[] cert = File.ReadAllBytes(certFile);
EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources.OfType<EmbeddedResource>().First(r => r.Name.Equals("Bit.Core.licensing.cer"));
@@ -29,37 +64,77 @@ internal class Program
moduleDefMd.Resources.Remove(embeddedResourceToRemove);
DataReader reader = embeddedResourceToRemove.CreateReader();
X509Certificate2 existingCert = new(reader.ReadRemainingBytes());
Console.WriteLine($"Existing Cert Thumbprint: {existingCert.Thumbprint}");
X509Certificate2 certificate = new(cert);
X509Certificate2 existingCert = X509CertificateLoader.LoadCertificate(reader.ReadRemainingBytes());
X509Certificate2 certificate = X509CertificateLoader.LoadCertificate(cert);
Console.WriteLine($"Existing certificate Thumbprint: {existingCert.Thumbprint}");
Console.WriteLine($"New certificate Thumbprint: {certificate.Thumbprint}");
Console.WriteLine($"New Cert Thumbprint: {certificate.Thumbprint}");
// Find LicensingService by class name (namespace-agnostic to handle renames)
TypeDef type = moduleDefMd.Types.FirstOrDefault(t => String.Equals(t.Name, "LicensingService", StringComparison.OrdinalIgnoreCase));
if (type == null)
{
Console.Error.WriteLine("ERROR: LicensingService class not found");
return -1;
}
Console.WriteLine($"Found: {type.FullName}");
IEnumerable<TypeDef> services = moduleDefMd.Types.Where(t => t.Namespace == "Bit.Core.Billing.Services");
TypeDef type = services.First(t => t.Name == "LicensingService");
MethodDef constructor = type.FindConstructors().First();
Instruction instructionToPatch = constructor.Body.Instructions.FirstOrDefault(i => i.OpCode == OpCodes.Ldstr && String.Equals((String)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase));
if (instructionToPatch != null)
if (constructor == null)
{
instructionToPatch.Operand = certificate.Thumbprint;
Console.Error.WriteLine("ERROR: Cannot find constructor");
return -1;
}
Instruction[] instructionToPatch = constructor.Body.Instructions
.Where(i => i.OpCode == OpCodes.Ldstr)
.Where(i => ((String)i.Operand)
.Contains(existingCert.Thumbprint, StringComparison.OrdinalIgnoreCase))
.ToArray();
if (instructionToPatch.Length > 0)
{
Console.WriteLine($"Found {instructionToPatch.Length} thumbprint Ldstr instruction(s) to replace");
foreach (Instruction inst in instructionToPatch)
{
Console.WriteLine($" Replacing: '{inst.Operand}'");
inst.Operand = certificate.Thumbprint;
}
}
else
{
Console.WriteLine("Can't find constructor to patch");
Console.WriteLine("ERROR: Can't find instruction to patch");
return -1;
}
Console.WriteLine("Writing: " + newCoreDll);
ModuleWriterOptions moduleWriterOptions = new(moduleDefMd);
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.KeepOldMaxStack;
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveAll;
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveRids;
moduleDefMd.Write(file + ".new");
moduleDefMd.Write(newCoreDll + ".new");
moduleDefMd.Dispose();
File.Delete(file);
File.Move(file + ".new", file);
File.Delete(newCoreDll);
File.Move(newCoreDll + ".new", newCoreDll);
}
foreach (String runtimeConfigFile in Directory.GetFiles("/app/mount/", "*.runtimeconfig.json", SearchOption.AllDirectories))
{
Console.WriteLine("Patching: " + runtimeConfigFile);
String[] lines = File.ReadAllLines(runtimeConfigFile);
for (Int32 i = 0; i < lines.Length; i++)
{
String line = lines[i];
if (!line.Contains("includedFrameworks", StringComparison.Ordinal)) continue;
lines[i] = lines[i].Replace("includedFrameworks", "frameworks", StringComparison.Ordinal);
break;
}
File.WriteAllText(runtimeConfigFile, String.Join("\n", lines), new UTF8Encoding(false));
}
return 0;

View File

@@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="dnlib" Version="4.5.0" />
<PackageReference Include="SingleFileExtractor.Core" Version="2.3.0" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /licenseGen
COPY . /licenseGen
@@ -8,7 +8,7 @@ COPY cert.pfx /app/
RUN dotnet restore
RUN dotnet publish -c Release -o /app --no-restore
FROM mcr.microsoft.com/dotnet/sdk:8.0
FROM mcr.microsoft.com/dotnet/sdk:10.0
WORKDIR /app
COPY --from=build /app .

View File

@@ -1,10 +1,14 @@
using McMaster.Extensions.CommandLineUtils;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Text.Json;
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using McMaster.Extensions.CommandLineUtils;
using System.Text.Json;
using Microsoft.IdentityModel.Tokens;
namespace licenseGen;
@@ -127,7 +131,7 @@ internal class Program
buff = Console.ReadLine();
if (buff is "" or "y" or "Y")
{
GenerateUserLicense(new X509Certificate2(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, guid, null);
GenerateUserLicense(X509CertificateLoader.LoadPkcs12FromFile(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, guid, null);
}
else
{
@@ -143,7 +147,7 @@ internal class Program
buff = Console.ReadLine();
if (buff is "" or "y" or "Y")
{
GenerateOrgLicense(new X509Certificate2(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, installid, businessName, null);
GenerateOrgLicense(X509CertificateLoader.LoadPkcs12FromFile(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, installid, businessName, null);
}
else
{
@@ -197,7 +201,7 @@ internal class Program
storageShort = (Int16) parsedStorage;
}
GenerateUserLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value);
GenerateUserLicense(X509CertificateLoader.LoadPkcs12FromFile(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value);
return 0;
});
@@ -243,7 +247,7 @@ internal class Program
storageShort = (Int16)parsedStorage;
}
GenerateOrgLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value);
GenerateOrgLicense(X509CertificateLoader.LoadPkcs12FromFile(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value);
return 0;
});
@@ -375,24 +379,28 @@ internal class Program
return;
}
Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
String licenseKey = String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key;
Set(type, license, "LicenseKey", licenseKey);
Set(type, license, "Id", userId);
Set(type, license, "Name", userName);
Set(type, license, "Email", email);
Set(type, license, "Premium", true);
Set(type, license, "MaxStorageGb", storage == 0 ? Int16.MaxValue : storage);
Set(type, license, "Version", 1);
Set(type, license, "Issued", DateTime.UtcNow);
Set(type, license, "Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
Set(type, license, "Expires", DateTime.UtcNow.AddYears(100));
DateTime issued = DateTime.UtcNow;
Set(type, license, "Issued", issued);
Set(type, license, "Refresh", issued.AddYears(100).AddMonths(-1));
Set(type, license, "Expires", issued.AddYears(100));
Set(type, license, "Trial", false);
Set(type, license, "LicenseType", Enum.Parse(licenseTypeEnum, "User"));
Set(type, license, "Token", GenerateUserToken(cert, userId, licenseKey, userName, email, storage, issued));
Set(type, license, "Hash", Convert.ToBase64String(((Byte[])computeHash.Invoke(license, []))!));
Set(type, license, "Signature", Convert.ToBase64String((Byte[])sign.Invoke(license, [cert])!));
Console.WriteLine(JsonSerializer.Serialize(license, JsonOptions));
}
private static void GenerateOrgLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid instalId, String businessName, String key)
private static void GenerateOrgLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid installId, String businessName, String key)
{
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath));
Type type = core.GetType("Bit.Core.Billing.Organizations.Models.OrganizationLicense");
@@ -431,12 +439,14 @@ internal class Program
return;
}
Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
Set(type, license, "InstallationId", instalId);
String licenseKey = String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key;
String businessNameFinal = String.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName;
Set(type, license, "LicenseKey", licenseKey);
Set(type, license, "InstallationId", installId);
Set(type, license, "Id", Guid.NewGuid());
Set(type, license, "Name", userName);
Set(type, license, "BillingEmail", email);
Set(type, license, "BusinessName", String.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName);
Set(type, license, "BusinessName", businessNameFinal);
Set(type, license, "Enabled", true);
Set(type, license, "Plan", "Enterprise (Annually)");
Set(type, license, "PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
@@ -458,10 +468,11 @@ internal class Program
Set(type, license, "UsersGetPremium", true);
Set(type, license, "UseCustomPermissions", true);
Set(type, license, "Version", 16);
Set(type, license, "Issued", DateTime.UtcNow);
Set(type, license, "Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
Set(type, license, "Expires", DateTime.UtcNow.AddYears(100));
Set(type, license, "ExpirationWithoutGracePeriod", DateTime.UtcNow.AddYears(100));
DateTime issued = DateTime.UtcNow;
Set(type, license, "Issued", issued);
Set(type, license, "Refresh", issued.AddYears(100).AddMonths(-1));
Set(type, license, "Expires", issued.AddYears(100));
Set(type, license, "ExpirationWithoutGracePeriod", issued.AddYears(100));
Set(type, license, "UsePasswordManager", true);
Set(type, license, "UseSecretsManager", true);
Set(type, license, "SmSeats", Int32.MaxValue);
@@ -472,6 +483,13 @@ internal class Program
Set(type, license, "UseOrganizationDomains", true);
Set(type, license, "UseAdminSponsoredFamilies", true);
Set(type, license, "UsePhishingBlocker", true);
Set(type, license, "UseAutomaticUserConfirmation", true);
Set(type, license, "UseDisableSmAdsForUsers", true);
Set(type, license, "UseMyItems", true);
Guid orgId = (Guid)type.GetProperty("Id").GetValue(license);
Set(type, license, "Token", GenerateOrgToken(cert, orgId, installId, licenseKey, email, businessNameFinal, userName, storage, planTypeEnum, issued));
Set(type, license, "Hash", Convert.ToBase64String((Byte[])computeHash.Invoke(license, [])!));
Set(type, license, "Signature", Convert.ToBase64String((Byte[])sign.Invoke(license, [cert])!));
@@ -481,4 +499,107 @@ internal class Program
{
type.GetProperty(name)?.SetValue(license, value);
}
private static String GenerateUserToken(X509Certificate2 cert, Guid userId, String licenseKey, String name, String email, Int16 maxStorageGb, DateTime now)
{
X509SecurityKey x509SecurityKey = new(cert);
SigningCredentials signingCredentials = new(x509SecurityKey, SecurityAlgorithms.RsaSha256);
DateTime expires = now.AddYears(100);
List<Claim> claims =
[
new("LicenseType", "User"),
new("LicenseKey", licenseKey),
new("Id", userId.ToString()),
new("Name", name),
new("Email", email),
new("Premium", "true"),
new("MaxStorageGb", (maxStorageGb == 0 ? Int16.MaxValue : maxStorageGb).ToString()),
new("Trial", "false"),
new("Issued", now.ToString("o")),
new("Expires", expires.ToString("o")),
new("Refresh", now.AddYears(100).AddMonths(-1).ToString("o"))
];
JwtSecurityTokenHandler handler = new();
JwtSecurityToken token = new(
issuer: "bitwarden",
audience: $"user:{userId}",
claims: claims,
notBefore: now,
expires: expires,
signingCredentials: signingCredentials);
return handler.WriteToken(token);
}
private static String GenerateOrgToken(X509Certificate2 cert, Guid orgId, Guid installationId, String licenseKey, String billingEmail, String businessName, String name, Int16 maxStorageGb, Type planTypeEnum, DateTime now)
{
X509SecurityKey x509SecurityKey = new(cert);
SigningCredentials signingCredentials = new(x509SecurityKey, SecurityAlgorithms.RsaSha256);
DateTime expires = now.AddYears(100);
// Resolve the integer value of EnterpriseAnnually from the runtime enum
Int32 planTypeInt = Convert.ToInt32(Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
List<Claim> claims =
[
new("LicenseType", "Organization"),
new("LicenseKey", licenseKey),
new("InstallationId", installationId.ToString()),
new("Id", orgId.ToString()),
new("Name", name),
new("BillingEmail", billingEmail),
new("BusinessName", businessName),
new("Enabled", "true"),
new("Plan", "Enterprise (Annually)"),
new("PlanType", planTypeInt.ToString()),
new("Seats", Int32.MaxValue.ToString()),
new("MaxCollections", Int16.MaxValue.ToString()),
new("MaxStorageGb", (maxStorageGb == 0 ? Int16.MaxValue : maxStorageGb).ToString()),
new("SelfHost", "true"),
new("UsersGetPremium", "true"),
new("UseGroups", "true"),
new("UseDirectory", "true"),
new("UseEvents", "true"),
new("UseTotp", "true"),
new("Use2fa", "true"),
new("UseApi", "true"),
new("UsePolicies", "true"),
new("UseSso", "true"),
new("UseResetPassword", "true"),
new("UseKeyConnector", "true"),
new("UseScim", "true"),
new("UseCustomPermissions", "true"),
new("UsePasswordManager", "true"),
new("UseSecretsManager", "true"),
new("SmSeats", Int32.MaxValue.ToString()),
new("SmServiceAccounts", Int32.MaxValue.ToString()),
new("UseRiskInsights", "true"),
new("UseAdminSponsoredFamilies", "true"),
new("UseOrganizationDomains", "true"),
new("UseAutomaticUserConfirmation", "true"),
new("UseDisableSmAdsForUsers", "true"),
new("UsePhishingBlocker", "true"),
new("UseMyItems", "true"),
new("LimitCollectionCreationDeletion", "true"),
new("AllowAdminAccessToAllCollectionItems", "true"),
new("Trial", "false"),
new("Issued", now.ToString("o")),
new("Expires", expires.ToString("o")),
new("Refresh", now.AddYears(100).AddMonths(-1).ToString("o")),
new("ExpirationWithoutGracePeriod", expires.ToString("o"))
];
JwtSecurityTokenHandler handler = new();
JwtSecurityToken token = new(
issuer: "bitwarden",
audience: $"organization:{orgId}",
claims: claims,
notBefore: now,
expires: expires,
signingCredentials: signingCredentials);
return handler.WriteToken(token);
}
}

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="5.1.0" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
</ItemGroup>
</Project>