This commit is contained in:
Michiel Hazelhof 2025-08-12 19:28:42 +02:00 committed by GitHub
commit 0764352448
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 358 additions and 310 deletions

View File

@ -12,7 +12,7 @@ jobs:
command: ./generateKeys.sh
- run:
name: Build script
command: ./build.sh y
command: ./build.sh update
- run:
name: Test generating user license
command: ./licenseGen.sh user TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23

2
.gitignore vendored
View File

@ -7,5 +7,5 @@ src/bitBetter/.vs/*
*.pem
.vscode/
*.pfx
*.cert
*.cer
*.vsidx

View File

@ -1,4 +1,4 @@
# Uncomment a line below and fill in the missing values or add your own. Every line in this file will be called by build.[sh|ps1] once the patched image is built.
# docker run -d --name bitwarden -v <full-local-path>\logs:/var/log/bitwarden -v <full-local-path>\bwdata:/etc/bitwarden -p 80:8080 --env-file <full-local-path>\settings.env bitwarden-patch
# docker run -d --name bitwarden -v <full-local-path>\logs:/var/log/bitwarden -v <full-local-path>\bwdata:/etc/bitwarden -p 80:8080 --env-file <full-local-path>\settings.env bitwarden-patched
# <OR>
# docker-compose -f <full-local-path>/docker-compose.yml up -d

View File

@ -46,8 +46,8 @@ If you wish to generate your self-signed cert & key manually, you can run the fo
```bash
cd .keys
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cert -days 36500 -outform DER -passout pass:test
openssl x509 -inform DER -in cert.cert -out cert.pem
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cer -days 36500 -outform DER -passout pass:test
openssl x509 -inform DER -in cert.cer -out cert.pem
openssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem -passin pass:test -passout pass:test
```
@ -70,7 +70,7 @@ From the BitBetter directory, simply run:
./build.[sh|ps1]
```
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 `ghcr.io/bitwarden/self-host` image called `bitwarden-patch`.
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 `ghcr.io/bitwarden/self-host` image called `bitwarden-patched`.
Afterwards it will automatically generate the license generator and start all previously specified containers which are **now ready to accept self-issued licenses.**

View File

@ -7,7 +7,7 @@ $tempdirectory = "$pwd\temp"
$components = "Api","Identity"
# delete old directories / files if applicable
if (Test-Path "$tempdirectory") {
if (Test-Path "$tempdirectory" -PathType Container) {
Remove-Item "$tempdirectory" -Recurse -Force
}
@ -19,24 +19,28 @@ if (Test-Path -Path "$pwd\src\licenseGen\cert.pfx" -PathType Leaf) {
Remove-Item "$pwd\src\licenseGen\cert.pfx" -Force
}
if (Test-Path -Path "$pwd\src\bitBetter\cert.cert" -PathType Leaf) {
Remove-Item "$pwd\src\bitBetter\cert.cert" -Force
if (Test-Path -Path "$pwd\src\bitBetter\cert.cer" -PathType Leaf) {
Remove-Item "$pwd\src\bitBetter\cert.cer" -Force
}
if (Test-Path "$pwd\.keys\cert.cert" -PathType Leaf) {
Rename-Item -Path "$pwd\.keys\cert.cert" -NewName "$pwd\.keys\cert.cer"
}
# generate keys if none are available
if (!(Test-Path "$pwd\.keys")) {
if (!(Test-Path "$pwd\.keys" -PathType Container)) {
.\generateKeys.ps1
}
# copy the key to bitBetter and licenseGen
Copy-Item "$pwd\.keys\cert.cert" -Destination "$pwd\src\bitBetter"
Copy-Item "$pwd\.keys\cert.pfx" -Destination "$pwd\src\licenseGen"
# copy the key to bitBetter
Copy-Item "$pwd\.keys\cert.cer" -Destination "$pwd\src\bitBetter"
# build bitBetter and clean the source directory after
docker build --no-cache -t bitbetter/bitbetter "$pwd\src\bitBetter"
Remove-Item "$pwd\src\bitBetter\cert.cert" -Force
Remove-Item "$pwd\src\bitBetter\cert.cer" -Force
# gather all running instances
# gather all running instances, cannot run a wildcard filter on Ancestor= :(
$oldinstances = docker container ps --all -f Name=bitwarden --format '{{.ID}}'
# stop and remove all running instances
@ -46,25 +50,35 @@ foreach ($instance in $oldinstances) {
}
# update bitwarden itself
if ($args[0] -eq 'y')
{
if ($args[0] -eq 'update') {
docker pull ghcr.io/bitwarden/self-host:beta
}
else
{
$confirmation = Read-Host "Update (or get) bitwarden source container"
} else {
$confirmation = Read-Host "Update (or get) bitwarden source container (y/n)"
if ($confirmation -eq 'y') {
docker pull ghcr.io/bitwarden/self-host:beta
}
}
# remove previous existing patch(ed) image
if (docker image ls -q bitwarden-patch) {
docker image rm bitwarden-patch
# stop and remove previous existing patch(ed) container
$oldinstances = docker container ps --all -f Ancestor=bitwarden-patched --format '{{.ID}}'
foreach ($instance in $oldinstances) {
docker stop $instance
docker rm $instance
}
$oldinstances = docker image ls bitwarden-patched --format '{{.ID}}'
foreach ($instance in $oldinstances) {
docker image rm $instance
}
# remove old extract containers
$oldinstances = docker container ps --all -f Name=bitwarden-extract --format '{{.ID}}'
foreach ($instance in $oldinstances) {
docker stop $instance
docker rm $instance
}
# start a new bitwarden instance so we can patch it
$patchinstance = docker run -d --name bitwarden-patch ghcr.io/bitwarden/self-host:beta
$patchinstance = docker run -d --name bitwarden-extract ghcr.io/bitwarden/self-host:beta
# create our temporary directory
New-item -ItemType Directory -Path $tempdirectory
@ -75,33 +89,38 @@ foreach ($component in $components) {
docker cp $patchinstance`:/app/$component/Core.dll "$tempdirectory\$component\Core.dll"
}
# stop and remove our temporary container
docker stop bitwarden-extract
docker rm bitwarden-extract
# run bitBetter, this applies our patches to the required files
docker run -v "$tempdirectory`:/app/mount" --rm bitbetter/bitbetter
# create a new image with the patched files
docker build . --tag bitwarden-patch --file "$pwd\src\bitBetter\Dockerfile-bitwarden-patch"
# stop and remove our temporary container
docker stop bitwarden-patch
docker rm bitwarden-patch
# copy our patched library to the licenseGen source directory
Copy-Item "$tempdirectory\Identity\Core.dll" -Destination "$pwd\src\licenseGen"
# remove our temporary directory
Remove-Item "$tempdirectory" -Recurse -Force
docker build . --tag bitwarden-patched --file "$pwd\src\bitBetter\Dockerfile-bitwarden-patch"
# start all user requested instances
foreach($line in Get-Content "$pwd\.servers\serverlist.txt") {
if (Test-Path -Path "$pwd\.servers\serverlist.txt" -PathType Leaf) {
foreach($line in Get-Content "$pwd\.servers\serverlist.txt") {
if (!($line.StartsWith("#"))) {
Invoke-Expression "& $line"
}
}
}
# remove our bitBetter image
docker image rm bitbetter/bitbetter
# copy our patched library to the licenseGen source directory
Copy-Item "$tempdirectory\Identity\Core.dll" -Destination "$pwd\src\licenseGen"
Copy-Item "$pwd\.keys\cert.pfx" -Destination "$pwd\src\licenseGen"
# build the licenseGen
docker build -t bitbetter/licensegen "$pwd\src\licenseGen"
# clean the licenseGen source directory
Remove-Item "$pwd\src\licenseGen\Core.dll" -Force
Remove-Item "$pwd\src\licenseGen\cert.pfx" -Force
# remove our temporary directory
Remove-Item "$tempdirectory" -Recurse -Force

View File

@ -20,8 +20,12 @@ if [ -f "$PWD/src/licenseGen/cert.pfx" ]; then
rm -f "$PWD/src/licenseGen/cert.pfx"
fi
if [ -f "$PWD/src/bitBetter/cert.cert" ]; then
rm -f "$PWD/src/bitBetter/cert.cert"
if [ -f "$PWD/src/bitBetter/cert.cer" ]; then
rm -f "$PWD/src/bitBetter/cert.cer"
fi
if [ -f "$PWD/.keys/cert.cert" ]; then
mv "$PWD/.keys/cert.cert" "$PWD/.keys/cert.cer"
fi
# generate keys if none are available
@ -29,15 +33,14 @@ if [ ! -d "$PWD/.keys" ]; then
./generateKeys.sh
fi
# copy the key to bitBetter and licenseGen
cp -f "$PWD/.keys/cert.cert" "$PWD/src/bitBetter"
cp -f "$PWD/.keys/cert.pfx" "$PWD/src/licenseGen"
# copy the key to bitBetter
cp -f "$PWD/.keys/cert.cer" "$PWD/src/bitBetter"
# build bitBetter and clean the source directory after
docker build --no-cache -t bitbetter/bitbetter "$PWD/src/bitBetter"
rm -f "$PWD/src/bitBetter/cert.cert"
rm -f "$PWD/src/bitBetter/cert.cer"
# gather all running instances
# gather all running instances, cannot run a wildcard filter on Ancestor= :(
OLDINSTANCES=$(docker container ps --all -f Name=bitwarden --format '{{.ID}}')
# stop and remove all running instances
@ -47,10 +50,10 @@ for INSTANCE in ${OLDINSTANCES[@]}; do
done
# update bitwarden itself
if [ "$1" = "y" ]; then
if [ "$1" = "update" ]; then
docker pull ghcr.io/bitwarden/self-host:beta
else
read -p "Update (or get) bitwarden source container: " -n 1 -r
read -p "Update (or get) bitwarden source container (y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
@ -58,13 +61,26 @@ else
fi
fi
# remove previous existing patch(ed) image
if [ "$(docker image ls -q bitwarden-patch)" ]; then
docker image rm bitwarden-patch
fi
# stop and remove previous existing patch(ed) container
OLDINSTANCES=$(docker container ps --all -f Ancestor=bitwarden-patched --format '{{.ID}}')
for INSTANCE in ${OLDINSTANCES[@]}; do
docker stop $INSTANCE
docker rm $INSTANCE
done
OLDINSTANCES=$(docker image ls bitwarden-patched --format '{{.ID}}')
for INSTANCE in ${OLDINSTANCES[@]}; do
docker image rm $INSTANCE
done
# remove old extract containers
OLDINSTANCES=$(docker container ps --all -f Name=bitwarden-extract --format '{{.ID}}')
for INSTANCE in ${OLDINSTANCES[@]}; do
docker stop $INSTANCE
docker rm $INSTANCE
done
# start a new bitwarden instance so we can patch it
PATCHINSTANCE=$(docker run -d --name bitwarden-patch ghcr.io/bitwarden/self-host:beta)
PATCHINSTANCE=$(docker run -d --name bitwarden-extract ghcr.io/bitwarden/self-host:beta)
# create our temporary directory
mkdir $TEMPDIRECTORY
@ -75,34 +91,39 @@ for COMPONENT in ${COMPONENTS[@]}; do
docker cp $PATCHINSTANCE:/app/$COMPONENT/Core.dll "$TEMPDIRECTORY/$COMPONENT/Core.dll"
done
# stop and remove our temporary container
docker stop bitwarden-extract
docker rm bitwarden-extract
# run bitBetter, this applies our patches to the required files
docker run -v "$TEMPDIRECTORY:/app/mount" --rm bitbetter/bitbetter
# create a new image with the patched files
docker build . --tag bitwarden-patch --file "$PWD/src/bitBetter/Dockerfile-bitwarden-patch"
# stop and remove our temporary container
docker stop bitwarden-patch
docker rm bitwarden-patch
# copy our patched library to the licenseGen source directory
cp -f "$TEMPDIRECTORY/Identity/Core.dll" "$PWD/src/licenseGen"
# remove our temporary directory
rm -rf "$TEMPDIRECTORY"
docker build . --tag bitwarden-patched --file "$PWD/src/bitBetter/Dockerfile-bitwarden-patch"
# start all user requested instances
sed -i 's/\r$//' "$PWD/.servers/serverlist.txt"
cat "$PWD/.servers/serverlist.txt" | while read -r LINE; do
if [ -f "$PWD/src/bitBetter/cert.cer" ]; then
sed -i 's/\r$//' "$PWD/.servers/serverlist.txt"
cat "$PWD/.servers/serverlist.txt" | while read -r LINE; do
if [[ $LINE == "#*" ]]; then
bash -c "$LINE"
done
fi
done
fi
# remove our bitBetter image
docker image rm bitbetter/bitbetter
# copy our patched library to the licenseGen source directory
cp -f "$TEMPDIRECTORY/Identity/Core.dll" "$PWD/src/licenseGen"
cp -f "$PWD/.keys/cert.pfx" "$PWD/src/licenseGen"
# build the licenseGen
docker build -t bitbetter/licensegen "$PWD/src/licenseGen"
# clean the licenseGen source directory
rm -f "$PWD/src/licenseGen/Core.dll"
rm -f "$PWD/src/licenseGen/cert.pfx"
# remove our temporary directory
rm -rf "$TEMPDIRECTORY"

View File

@ -20,6 +20,6 @@ if (Test-Path "$pwd\.keys")
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.cert`" -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.cert`" -out `"$pwd\.keys\cert.pem`""
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"

View File

@ -15,6 +15,6 @@ fi
mkdir "$DIR"
# Generate new keys
openssl req -x509 -newkey rsa:4096 -keyout "$DIR/key.pem" -out "$DIR/cert.cert" -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.cert" -out "$DIR/cert.pem"
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

View File

@ -10,7 +10,7 @@ if ($($args.Count) -lt 1) {
Exit 1
}
if ($args[0] = "interactive") {
if ($args[0] -eq "interactive") {
docker run -it --rm bitbetter/licensegen interactive
} else {
docker run bitbetter/licensegen $args

View File

@ -2,7 +2,7 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /bitBetter
COPY . /bitBetter
COPY cert.cert /app/
COPY cert.cer /app/
RUN dotnet restore
RUN dotnet publish -c Release -o /app --no-restore
@ -11,4 +11,4 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT [ "/app/bitBetter" ]
ENTRYPOINT ["dotnet", "/app/bitBetter.dll"]

View File

@ -1,4 +1,3 @@
FROM ghcr.io/bitwarden/self-host:beta
COPY ./temp/Api/Core.dll /app/Api/Core.dll
COPY ./temp/Identity/Core.dll /app/Identity/Core.dll
COPY ./temp/ /app/

View File

@ -14,7 +14,7 @@ internal class Program
{
private static Int32 Main()
{
const String certFile = "/app/cert.cert";
const String certFile = "/app/cert.cer";
String[] files = Directory.GetFiles("/app/mount", "Core.dll", SearchOption.AllDirectories);
foreach (String file in files)
@ -23,12 +23,7 @@ internal class Program
ModuleDefMD moduleDefMd = ModuleDefMD.Load(file);
Byte[] cert = File.ReadAllBytes(certFile);
EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources
.OfType<EmbeddedResource>()
.First(r => r.Name.Equals("Bit.Core.licensing.cer"));
Console.WriteLine(embeddedResourceToRemove.Name);
EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources.OfType<EmbeddedResource>().First(r => r.Name.Equals("Bit.Core.licensing.cer"));
EmbeddedResource embeddedResourceToAdd = new("Bit.Core.licensing.cer", cert) { Attributes = embeddedResourceToRemove.Attributes };
moduleDefMd.Resources.Add(embeddedResourceToAdd);
moduleDefMd.Resources.Remove(embeddedResourceToRemove);
@ -45,10 +40,7 @@ internal class Program
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));
Instruction instructionToPatch = constructor.Body.Instructions.FirstOrDefault(i => i.OpCode == OpCodes.Ldstr && String.Equals((String)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase));
if (instructionToPatch != null)
{

View File

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

View File

@ -12,4 +12,4 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT [ "dotnet", "/app/licenseGen.dll", "--core", "/app/Core.dll", "--cert", "/app/cert.pfx" ]
ENTRYPOINT ["dotnet", "/app/licenseGen.dll", "--cert=/app/cert.pfx", "--core=/app/Core.dll"]

View File

@ -1,57 +1,31 @@
using System;
using System.IO;
using System.Text.Json;
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Cryptography.X509Certificates;
using McMaster.Extensions.CommandLineUtils;
using Newtonsoft.Json;
namespace licenseGen;
internal class Program
{
private static readonly CommandLineApplication App = new();
private static readonly CommandOption Cert = App.Option("--cert", "Certifcate file", CommandOptionType.SingleValue);
private static readonly CommandOption CoreDll = App.Option("--core", "Path to Core.dll", CommandOptionType.SingleValue);
private static Int32 Main(String[] args)
{
CommandLineApplication app = new();
CommandOption cert = app.Option("--cert", "cert file", CommandOptionType.SingleValue);
CommandOption coreDll = app.Option("--core", "path to core dll", CommandOptionType.SingleValue);
Boolean CertExists()
{
return File.Exists(cert.Value());
}
Boolean CoreExists()
{
return File.Exists(coreDll.Value());
}
Boolean VerifyTopOptions()
{
return !String.IsNullOrWhiteSpace(cert.Value()) &&
!String.IsNullOrWhiteSpace(coreDll.Value()) &&
CertExists() && CoreExists();
}
app.Command("interactive", config =>
App.Command("interactive", config =>
{
String buff, licenseType = "", name = "", email = "", businessName="";
Int16 storage = 0;
Boolean validGuid = false, validInstallid = false;
Guid guid = new(), installid = new();
Guid guid = Guid.Empty, installid = Guid.Empty;
config.OnExecute(() =>
{
if (!VerifyTopOptions())
{
if (!CoreExists()) config.Error.WriteLine($"Can't find core dll at: {coreDll.Value()}");
if (!CertExists()) config.Error.WriteLine($"Can't find certificate at: {cert.Value()}");
config.ShowHelp();
return 1;
}
Check();
Console.WriteLine("Interactive license mode...");
while (licenseType == "")
@ -59,12 +33,14 @@ internal class Program
Console.WriteLine("What would you like to generate, a [u]ser license or an [o]rg license: ");
buff = Console.ReadLine();
if(buff == "u")
switch (buff)
{
case "u":
{
licenseType = "user";
Console.WriteLine("Okay, we will generate a user license.");
while (validGuid == false)
while (!validGuid)
{
Console.WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]: ");
buff = Console.ReadLine();
@ -72,13 +48,14 @@ internal class Program
if (Guid.TryParse(buff, out guid))validGuid = true;
else Console.WriteLine("The user-guid provided does not appear to be valid!");
}
break;
}
else if (buff == "o")
case "o":
{
licenseType = "org";
Console.WriteLine("Okay, we will generate an organization license.");
while (validInstallid == false)
while (!validInstallid)
{
Console.WriteLine("Please provide your Bitwarden Install-ID — refer to the Readme for details on how to retrieve this. [Install-ID]: ");
buff = Console.ReadLine();
@ -100,10 +77,11 @@ internal class Program
businessName = buff;
}
}
break;
}
else
{
default:
Console.WriteLine("Unrecognized option \'" + buff + "\'.");
break;
}
}
@ -111,7 +89,7 @@ internal class Program
{
Console.WriteLine("Please provide the username this license will be registered to. [username]: ");
buff = Console.ReadLine();
if ( CheckUsername(buff) ) name = buff;
if (CheckUsername(buff)) name = buff;
}
while (email == "")
@ -141,39 +119,46 @@ internal class Program
}
}
if (licenseType == "user")
switch (licenseType)
{
case "user":
{
Console.WriteLine("Confirm creation of \"user\" license for username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", User-GUID: \"" + guid + "\"? Y/n");
buff = Console.ReadLine();
if ( buff is "" or "y" or "Y" )
if (buff is "" or "y" or "Y")
{
GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name, email, storage, guid, null);
GenerateUserLicense(new X509Certificate2(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, guid, null);
}
else
{
Console.WriteLine("Exiting...");
return 0;
}
break;
}
else if (licenseType == "org")
case "org":
{
Console.WriteLine("Confirm creation of \"organization\" license for business name: \"" + businessName + "\", username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", Install-ID: \"" + installid + "\"? Y/n");
buff = Console.ReadLine();
if ( buff is "" or "y" or "Y" )
if (buff is "" or "y" or "Y")
{
GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name, email, storage, installid, businessName, null);
GenerateOrgLicense(new X509Certificate2(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, installid, businessName, null);
}
else
{
Console.WriteLine("Exiting...");
return 0;
}
break;
}
}
return 0;
});
});
app.Command("user", config =>
App.Command("user", config =>
{
CommandArgument name = config.Argument("Name", "your name");
CommandArgument email = config.Argument("Email", "your email");
@ -183,20 +168,7 @@ internal class Program
config.OnExecute(() =>
{
if (!VerifyTopOptions())
{
if (!CoreExists())
{
config.Error.WriteLine($"Can't find core dll at: {coreDll.Value()}");
}
if (!CertExists())
{
config.Error.WriteLine($"Can't find certificate at: {cert.Value()}");
}
config.ShowHelp();
return 1;
}
Check();
if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value))
{
@ -225,12 +197,12 @@ internal class Program
storageShort = (Int16) parsedStorage;
}
GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value);
GenerateUserLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value);
return 0;
});
});
app.Command("org", config =>
App.Command("org", config =>
{
CommandArgument name = config.Argument("Name", "your name");
CommandArgument email = config.Argument("Email", "your email");
@ -241,24 +213,9 @@ internal class Program
config.OnExecute(() =>
{
if (!VerifyTopOptions())
{
if (!CoreExists())
{
config.Error.WriteLine($"Can't find core dll at: {coreDll.Value()}");
}
if (!CertExists())
{
config.Error.WriteLine($"Can't find certificate at: {cert.Value()}");
}
Check();
config.ShowHelp();
return 1;
}
if (String.IsNullOrWhiteSpace(name.Value) ||
String.IsNullOrWhiteSpace(email.Value) ||
String.IsNullOrWhiteSpace(installId.Value))
if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value) || String.IsNullOrWhiteSpace(installId.Value))
{
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}' InstallId='{installId.Value}'");
config.ShowHelp(true);
@ -283,34 +240,54 @@ internal class Program
config.ShowHelp(true);
return 1;
}
storageShort = (Int16) parsedStorage;
storageShort = (Int16)parsedStorage;
}
GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value);
GenerateOrgLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value);
return 0;
});
});
app.OnExecute(() =>
App.OnExecute(() =>
{
app.ShowHelp();
App.ShowHelp();
return 10;
});
app.HelpOption("-? | -h | --help");
try
{
return app.Execute(args);
App.HelpOption("-? | -h | --help");
return App.Execute(args);
}
catch (Exception e)
catch (Exception exception)
{
Console.Error.WriteLine("Oops: {0}", e);
Console.Error.WriteLine("Oops: {0}", exception);
return 100;
}
}
private static void Check()
{
if (!File.Exists(Cert.Value()))
{
App.Error.WriteLine($"Can't find certificate at: {Cert.Value()}");
App.ShowHelp();
Environment.Exit(1);
}
if (!File.Exists(CoreDll.Value()))
{
App.Error.WriteLine($"Can't find core dll at: {CoreDll.Value()}");
App.ShowHelp();
Environment.Exit(1);
}
if (Cert == null || String.IsNullOrWhiteSpace(Cert.Value()) || CoreDll == null || String.IsNullOrWhiteSpace(CoreDll.Value()))
{
App.ShowHelp();
Environment.Exit(1);
}
}
// checkUsername Checks that the username is a valid username
private static Boolean CheckUsername(String s)
{
@ -356,99 +333,146 @@ internal class Program
return false;
}
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
private static void GenerateUserLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid userId, String key)
{
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath);
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath));
Type type = core.GetType("Bit.Core.Billing.Models.Business.UserLicense");
Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType");
if (type == null)
{
Console.WriteLine("Could not find type!");
return;
}
if (licenseTypeEnum == null)
{
Console.WriteLine("Could not find license licenseTypeEnum!");
return;
}
Object license = Activator.CreateInstance(type);
Set("LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
Set("Id", userId);
Set("Name", userName);
Set("Email", email);
Set("Premium", true);
Set("MaxStorageGb", storage == 0 ? Int16.MaxValue : storage);
Set("Version", 1);
Set("Issued", DateTime.UtcNow);
Set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
Set("Expires", DateTime.UtcNow.AddYears(100));
Set("Trial", false);
Set("LicenseType", Enum.Parse(licenseTypeEnum, "User"));
Set("Hash", Convert.ToBase64String((Byte[])type.GetMethod("ComputeHash").Invoke(license, [])));
Set("Signature", Convert.ToBase64String((Byte[])type.GetMethod("Sign").Invoke(license, [cert])));
Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented));
return;
void Set(String name, Object value)
MethodInfo computeHash = type.GetMethod("ComputeHash");
if (computeHash == null)
{
type.GetProperty(name)?.SetValue(license, value);
}
Console.WriteLine("Could not find ComputeHash!");
return;
}
MethodInfo sign = type.GetMethod("Sign");
if (sign == null)
{
Console.WriteLine("Could not find sign!");
return;
}
Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
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));
Set(type, license, "Trial", false);
Set(type, license, "LicenseType", Enum.Parse(licenseTypeEnum, "User"));
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)
{
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath);
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath));
Type type = core.GetType("Bit.Core.Billing.Organizations.Models.OrganizationLicense");
Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType");
Type planTypeEnum = core.GetType("Bit.Core.Billing.Enums.PlanType");
if (type == null)
{
Console.WriteLine("Could not find type!");
return;
}
if (licenseTypeEnum == null)
{
Console.WriteLine("Could not find licenseTypeEnum!");
return;
}
if (planTypeEnum == null)
{
Console.WriteLine("Could not find planTypeEnum!");
return;
}
Object license = Activator.CreateInstance(type);
Set("LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
Set("InstallationId", instalId);
Set("Id", Guid.NewGuid());
Set("Name", userName);
Set("BillingEmail", email);
Set("BusinessName", String.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName);
Set("Enabled", true);
Set("Plan", "Enterprise (Annually)");
Set("PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
Set("Seats", Int32.MaxValue);
Set("MaxCollections", Int16.MaxValue);
Set("UsePolicies", true);
Set("UseSso", true);
Set("UseKeyConnector", true);
Set("UseScim", true);
Set("UseGroups", true);
Set("UseEvents", true);
Set("UseDirectory", true);
Set("UseTotp", true);
Set("Use2fa", true);
Set("UseApi", true);
Set("UseResetPassword", true);
Set("UseCustomPermissions", true);
Set("MaxStorageGb", storage == 0 ? Int16.MaxValue : storage);
Set("SelfHost", true);
Set("UsersGetPremium", true);
Set("UsePasswordManager", true);
Set("UseSecretsManager", true);
Set("SmSeats", Int32.MaxValue);
Set("SmServiceAccounts", Int32.MaxValue);
Set("Version", 15); //This is set to 15 to use AllowAdminAccessToAllCollectionItems can be changed to 13 to just use Secrets Manager
Set("Issued", DateTime.UtcNow);
Set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
Set("Expires", DateTime.UtcNow.AddYears(100));
Set("Trial", false);
Set("LicenseType", Enum.Parse(licenseTypeEnum, "Organization"));
Set("LimitCollectionCreationDeletion", true); //This will be used in the new version of BitWarden but can be applied now
Set("AllowAdminAccessToAllCollectionItems", true);
Set("UseRiskInsights", true);
Set("UseOrganizationDomains", true);
Set("UseAdminSponsoredFamilies", true);
Set("Hash", Convert.ToBase64String((Byte[])type.GetMethod("ComputeHash").Invoke(license, [])));
Set("Signature", Convert.ToBase64String((Byte[])type.GetMethod("Sign").Invoke(license, [cert])));
Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented));
MethodInfo computeHash = type.GetMethod("ComputeHash");
if (computeHash == null)
{
Console.WriteLine("Could not find ComputeHash!");
return;
}
void Set(String name, Object value)
MethodInfo sign = type.GetMethod("Sign");
if (sign == null)
{
Console.WriteLine("Could not find sign!");
return;
}
Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
Set(type, license, "InstallationId", instalId);
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, "Enabled", true);
Set(type, license, "Plan", "Enterprise (Annually)");
Set(type, license, "PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
Set(type, license, "Seats", Int32.MaxValue);
Set(type, license, "MaxCollections", Int16.MaxValue);
Set(type, license, "UsePolicies", true);
Set(type, license, "UseSso", true);
Set(type, license, "UseKeyConnector", true);
Set(type, license, "UseScim", true);
Set(type, license, "UseGroups", true);
Set(type, license, "UseEvents", true);
Set(type, license, "UseDirectory", true);
Set(type, license, "UseTotp", true);
Set(type, license, "Use2fa", true);
Set(type, license, "UseApi", true);
Set(type, license, "UseResetPassword", true);
Set(type, license, "MaxStorageGb", storage == 0 ? Int16.MaxValue : storage);
Set(type, license, "SelfHost", true);
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));
Set(type, license, "UsePasswordManager", true);
Set(type, license, "UseSecretsManager", true);
Set(type, license, "SmSeats", Int32.MaxValue);
Set(type, license, "SmServiceAccounts", Int32.MaxValue);
Set(type, license, "UseRiskInsights", true);
Set(type, license, "LimitCollectionCreationDeletion", true);
Set(type, license, "AllowAdminAccessToAllCollectionItems", true);
Set(type, license, "Trial", false);
Set(type, license, "LicenseType", Enum.Parse(licenseTypeEnum, "Organization"));
Set(type, license, "UseOrganizationDomains", true);
Set(type, license, "UseAdminSponsoredFamilies", true);
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 Set(Type type, Object license, String name, Object value)
{
type.GetProperty(name)?.SetValue(license, value);
}
}
}

View File

@ -1,14 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
</ItemGroup>
</Project>