diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 34a1058..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: 2 -jobs: - build: - machine: true - steps: - - checkout - - run: - name: Print the Current Time - command: date - - run: - name: Generate Keys - command: ./.keys/generate-keys.sh - - run: - name: Build script - command: ./build.sh diff --git a/.keys/generate-keys.sh b/.keys/generate-keys.sh deleted file mode 100755 index d460c13..0000000 --- a/.keys/generate-keys.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -# Check for openssl -command -v openssl >/dev/null 2>&1 || { echo >&2 "openssl required but not found. Aborting."; exit 1; } - -DIR=`dirname "$0"` -DIR=`exec 2>/dev/null;(cd -- "$DIR") && cd -- "$DIR"|| cd "$DIR"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd` - -# Remove any existing key files -[ ! -e "$DIR/cert.pem" ] || rm "$DIR/cert.pem" -[ ! -e "$DIR/key.pem" ] || rm "$DIR/key.pem" -[ ! -e "$DIR/cert.cert" ] || rm "$DIR/cert.cert" -[ ! -e "$DIR/cert.pfx" ] || rm "$DIR/cert.pfx" - -# 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 pkcs12 -export -out "$DIR/cert.pfx" -inkey "$DIR/key.pem" -in "$DIR/cert.pem" -passin pass:test -passout pass:test - -ls diff --git a/.servers/serverlist.txt b/.servers/serverlist.txt new file mode 100644 index 0000000..2ca4fb7 --- /dev/null +++ b/.servers/serverlist.txt @@ -0,0 +1 @@ +docker run -d --name bitwarden -v :/etc/bitwarden -p 80:8080 --env-file \settings.env bitwarden-patch \ No newline at end of file diff --git a/README.md b/README.md index 7ded320..651d273 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # BitBetter -BitBetter is is a tool to modify Bitwarden's core dll to allow you to generate your own individual and organisation licenses. **You must have an existing installation of Bitwarden for BitBetter to modify.** +BitBetter is is a tool to modify Bitwarden's core dll to allow you to generate your own individual and organisation licenses. Please see the FAQ below for details on why this software was created. -_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 some semi 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 @@ -25,55 +25,69 @@ Credit to https://github.com/h44z/BitBetter and https://github.com/jakeswenson/B - [Footnotes](#footnotes) # Getting Started -The following instructions are for unix-based systems (Linux, BSD, macOS), it is possible to use a Windows systems assuming you are able to enable and install [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10). +The following instructions are for unix-based systems (Linux, BSD, macOS) and Windows, just choose the correct script extension (.sh or .ps1 respectively). ## Dependencies Aside from docker, which you also need for Bitwarden, BitBetter requires the following: * Bitwarden (tested with 1.47.1, might work on lower versions) -* openssl (probably already installed on most Linux or WSL systems, any version should work) +* openssl (probably already installed on most Linux or WSL systems, any version should work, on Windows it will be auto installed using winget) ## Setting up BitBetter With your dependencies installed, begin the installation of BitBetter by downloading it through Github or using the git command: -```bash +``` git clone https://github.com/jakeswenson/BitBetter.git ``` ## Building BitBetter -Now that you've set up your build environment, you can **run the main build script** to generate a modified version of the `bitwarden/api` and `bitwarden/identity` docker images. +Now that you've set up your build environment, we need to specify which servers to start after the work is done. +The scripts supports running and patching multi instances. + +Edit the .servers/serverlist.txt file and fill in the missing values, they can be replaced with existing installation values. + +Now it is time to **run the main build script** to generate a modified version of the `bitwarden/self-host` docker image and the license generator. From the BitBetter directory, simply run: -```bash -./build.sh +``` +./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 `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/self-host` image called `bitwarden-patch`. -You may now simply create the file `/path/to/bwdata/docker/docker-compose.override.yml` with the following contents to utilize the modified images. +Afterwards it will automatically generate the license generator and start all previously specified containers which are **now ready to accept self-issued licenses.** -```yaml -version: '3' - -services: - api: - image: bitbetter/api - - identity: - image: bitbetter/identity -``` - -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`. - -> Replace `dockerComposePull`
with `#dockerComposePull` - -You can now start or restart Bitwarden as normal and the modified api will be used. **It is now ready to accept self-issued licenses.** --- + +## Updating Bitwarden and BitBetter + +To update Bitwarden, the same `build.[sh|ps1]` script can be used. It will rebuild the BitBetter image and automatically update Bitwarden before doing so. + +## Generating Signed Licenses + +There is a tool included in the directory `licenseGen/` that will generate new individual and organization licenses. These licenses will be accepted by the modified Bitwarden because they will be signed by the certificate you generated in earlier steps. + +In order to run the tool and generate a license you'll need to get a **user's GUID** in order to generate an **invididual license** or the server's **install ID** to generate an **Organization license**. These can be retrieved most easily through the Bitwarden [Admin Portal](https://help.bitwarden.com/article/admin-portal/). + +**The user must have a verified email address at the time of license import, otherwise Bitwarden will reject the license key. Nevertheless, the license key can be generated even before the user's email is verified.** + +If you ran the build script, you can **simply run the license gen in interactive mode** from the `Bitbetter` directory and **follow the prompts to generate your license**. + +``` +./licenseGen.[sh|ps1] interactive +``` + +**The license generator will spit out a JSON-formatted license which can then be used within the Bitwarden web front-end to license your user or org!** + + +--- + ### Note: Manually generating Certificate & Key If you wish to generate your self-signed cert & key manually, you can run the following commands. +Note that you should never have to do this yourself, but can also be triggered by running the generateKeys.[sh|ps1] script. ```bash openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cert -days 36500 -outform DER -passout pass:test @@ -81,69 +95,11 @@ openssl x509 -inform DER -in cert.cert -out cert.pem openssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem -passin pass:test -passout pass:test ``` -> Note that the password here must be `test`.[1](#f1) +> Note that the password here must be `test`.[2](#f1) + --- -## Updating Bitwarden and BitBetter - -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. - -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 -./update-bitwarden.sh param1 param2 param3 -``` -`param1`: The path to the directory containing your bwdata directory - -`param2`: If you want the docker-compose file to be overwritten (either `y` or `n`) - -`param3`: If you want the bitbetter images to be rebuild (either `y` or `n`) - -If you are updating from versions <= 1.46.2, you may need to run `update-bitwarden.sh` twice to complete the update process. - -## Generating Signed Licenses - -There is a tool included in the directory `src/licenseGen/` that will generate new individual and organization licenses. These licenses will be accepted by the modified Bitwarden because they will be signed by the certificate you generated in earlier steps. - - -First, from the `BitBetter/src/licenseGen` directory, **build the license generator**.[2](#f2) - -```bash -./build.sh -``` - -In order to run the tool and generate a license you'll need to get a **user's GUID** in order to generate an **invididual license** or the server's **install ID** to generate an **Organization license**. These can be retrieved most easily through the Bitwarden [Admin Portal](https://help.bitwarden.com/article/admin-portal/). - -**The user must have a verified email address at the time of license import, otherwise Bitwarden will reject the license key. Nevertheless, the license key can be generated even before the user's email is verified.** - -If you generated your keys in the default `BitBetter/.keys` directory, you can **simply run the license gen in interactive mode** from the `Bitbetter` directory and **follow the prompts to generate your license**. - -```bash -./src/licenseGen/run.sh interactive -``` - -**The license generator will spit out a JSON-formatted license which can then be used within the Bitwarden web front-end to license your user or org!** - ---- - -### Note: Alternative Ways to Generate License - -If you wish to run the license gen from a directory aside from the root `BitBetter` one, you'll have to provide the absolute path to your cert.pfx. - -```bash -./src/licenseGen/run.sh /Absolute/Path/To/BitBetter/.keys/cert.pfx interactive -``` - -Additional, instead of interactive mode, you can also pass the parameters directly to the command as follows. - -```bash -./src/licenseGen/run.sh /Absolute/Path/To/BitBetter/.keys/cert.pfx user "Name" "E-Mail" "User-GUID" ["Storage Space in GB"] ["Custom LicenseKey"] -./src/licenseGen/run.sh /Absolute/Path/To/BitBetter/.keys/cert.pfx org "Name" "E-Mail" "Install-ID used to install the server" ["Storage Space in GB"] ["Custom LicenseKey"] -``` - ---- - - # FAQ: Questions you might have. ## Why build a license generator for open source software? @@ -161,7 +117,6 @@ UPDATE: Bitwarden now offers a cheap license called [Families Organization](http # Footnotes -1 If you wish to change this you'll need to change the value that `src/licenseGen/Program.cs` uses for its `GenerateUserLicense` and `GenerateOrgLicense` calls. Remember, this is really unnecessary as this certificate does not represent any type of security-related certificate. - -2This tool builds on top of the `bitbetter/api` container image so make sure you've built that above using the root `./build.sh` script. +2This tool builds on top of the `bitbetter/api` container image so make sure you've built that above using the root `./build.sh` script. +1 If you wish to change this you'll need to change the value that `licenseGen/Program.cs` uses for its `GenerateUserLicense` and `GenerateOrgLicense` calls. Remember, this is really unnecessary as this certificate does not represent any type of security-related certificate. diff --git a/bitBetter/Dockerfile b/bitBetter/Dockerfile new file mode 100644 index 0000000..00555b7 --- /dev/null +++ b/bitBetter/Dockerfile @@ -0,0 +1,14 @@ +FROM mcr.microsoft.com/dotnet/sdk:6.0 as build +WORKDIR /bitBetter + +COPY . /bitBetter +COPY cert.cert /app/ + +RUN dotnet restore +RUN dotnet publish -c Release -o /app --no-restore + +FROM mcr.microsoft.com/dotnet/sdk:6.0 +WORKDIR /app +COPY --from=build /app . + +ENTRYPOINT [ "/app/bitBetter" ] \ No newline at end of file diff --git a/bitBetter/Program.cs b/bitBetter/Program.cs new file mode 100644 index 0000000..81f8f86 --- /dev/null +++ b/bitBetter/Program.cs @@ -0,0 +1,80 @@ +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; + +namespace bitBetter; + +internal class Program +{ + private static Int32 Main(String[] args) + { + const String certFile = "/app/cert.cert"; + String[] files = Directory.GetFiles("/app/mount", "Core.dll", SearchOption.AllDirectories); + + Console.WriteLine(files.Length.ToString()); + + foreach (String file in files) + { + Console.WriteLine(file); + ModuleDefMD moduleDefMd = ModuleDefMD.Load(file); + Byte[] cert = File.ReadAllBytes(certFile); + + EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources + .OfType() + .First(r => r.Name.Equals("Bit.Core.licensing.cer")); + + Console.WriteLine(embeddedResourceToRemove.Name); + + EmbeddedResource embeddedResourceToAdd = new("Bit.Core.licensing.cer", cert) {Attributes = embeddedResourceToRemove.Attributes }; + moduleDefMd.Resources.Add(embeddedResourceToAdd); + moduleDefMd.Resources.Remove(embeddedResourceToRemove); + + DataReader reader = embeddedResourceToRemove.CreateReader(); + X509Certificate2 existingCert = new(reader.ReadRemainingBytes()); + + Console.WriteLine($"Existing Cert Thumbprint: {existingCert.Thumbprint}"); + X509Certificate2 certificate = new(cert); + + Console.WriteLine($"New Cert Thumbprint: {certificate.Thumbprint}"); + + IEnumerable services = moduleDefMd.Types.Where(t => t.Namespace == "Bit.Core.Services"); + TypeDef type = services.First(t => t.Name == "LicensingService"); + MethodDef constructor = type.FindConstructors().First(); + + Instruction instToReplace = + constructor.Body.Instructions + .FirstOrDefault(i => i.OpCode == OpCodes.Ldstr + && String.Equals((String)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase)); + + if (instToReplace != null) + { + Int32 index = constructor.Body.Instructions.IndexOf(instToReplace); + constructor.Body.Instructions.Remove(instToReplace); + constructor.Body.Instructions.Insert(index, Instruction.Create(OpCodes.Ldstr, certificate.Thumbprint)); + } + else + { + Console.WriteLine("Cant find inst"); + } + + ModuleWriterOptions moduleWriterOptions = new(moduleDefMd); + moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.KeepOldMaxStack; + moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveAll; + moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveRids; + + moduleDefMd.Write(file + ".new"); + moduleDefMd.Dispose(); + File.Delete(file); + File.Move(file + ".new", file); + } + + return 0; + } +} \ No newline at end of file diff --git a/src/bitBetter/bitBetter.csproj b/bitBetter/bitBetter.csproj similarity index 76% rename from src/bitBetter/bitBetter.csproj rename to bitBetter/bitBetter.csproj index 43d79ed..8dd1aff 100644 --- a/src/bitBetter/bitBetter.csproj +++ b/bitBetter/bitBetter.csproj @@ -5,8 +5,8 @@ netcoreapp6.0 - - + + diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..f847b33 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,74 @@ +if (Test-Path "$pwd\temp") { + Remove-Item "$pwd\temp" -Recurse -Force +} + +if (Test-Path -Path "$pwd\licenseGen\Core.dll" -PathType Leaf) { + Remove-Item "$pwd\licenseGen\Core.dll" -Force +} + +if (Test-Path -Path "$pwd\licenseGen\cert.pfx" -PathType Leaf) { + Remove-Item "$pwd\licenseGen\cert.pfx" -Force +} + +if (Test-Path -Path "$pwd\bitBetter\cert.cert" -PathType Leaf) { + Remove-Item "$pwd\bitBetter\cert.cert" -Force +} + +if (!(Test-Path "$pwd\.keys")) { + .\generateKeys.ps1 +} + +Copy-Item "$pwd\.keys\cert.cert" -Destination "$pwd\bitBetter" +Copy-Item "$pwd\.keys\cert.pfx" -Destination "$pwd\licenseGen" +docker build -t bitbetter/bitbetter "$pwd\bitBetter" +Remove-Item "$pwd\bitBetter\cert.cert" -Force + +$components = "Api","Identity" +$oldinstances = docker container ps --all -f Name=bitwarden --format '{{.ID}}' + +foreach ($instance in $oldinstances) { + docker stop $instance + docker rm $instance +} + +docker pull bitwarden/self-host:beta + +docker stop bitwarden-patch +docker rm bitwarden-patch +docker image rm bitwarden-patch +$patchinstance = docker run -d --name bitwarden-patch bitwarden/self-host:beta + +New-item -ItemType Directory -Path "$pwd\temp" +foreach ($component in $components) { + New-item -itemtype Directory -path "$pwd\temp\$component" + docker cp $patchinstance`:/app/$component/Core.dll "$pwd\temp\$component\Core.dll" +} + +docker run -v "$pwd\temp:/app/mount" --rm bitbetter/bitbetter + +foreach ($component in $components) { + docker cp -a "$pwd\temp\$component\Core.dll" $patchinstance`:/app/$component/Core.dll +} + +Copy-Item "$pwd\temp\Identity\Core.dll" -Destination "$pwd\licenseGen" +Remove-Item "$pwd\temp" -Recurse -Force + +docker commit $patchinstance bitwarden-patch +docker stop bitwarden-patch +docker rm bitwarden-patch + +$newinstances = @() +foreach($line in Get-Content "$pwd\.servers\serverlist.txt") { + $newinstace = @(Invoke-Expression "& $line") + $newinstances += $newinstace +} + +foreach ($instance in $newinstances) { + docker start $instance +} + +docker image rm bitbetter/bitbetter + +docker build -t bitbetter/licensegen "$pwd\licenseGen" +Remove-Item "$pwd\licenseGen\Core.dll" -Force +Remove-Item "$pwd\licenseGen\cert.pfx" -Force \ No newline at end of file diff --git a/build.sh b/build.sh deleted file mode 100755 index 4371c0c..0000000 --- a/build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -DIR=`dirname "$0"` -DIR=`exec 2>/dev/null;(cd -- "$DIR") && cd -- "$DIR"|| cd "$DIR"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd` -BW_VERSION=$(curl -sL https://go.btwrdn.co/bw-sh-versions | grep '^ *"'coreVersion'":' | awk -F\: '{ print $2 }' | sed -e 's/,$//' -e 's/^"//' -e 's/"$//') - -echo "Building BitBetter for BitWarden version $BW_VERSION" - -# If there aren't any keys, generate them first. -[ -e "$DIR/.keys/cert.cert" ] || "$DIR/.keys/generate-keys.sh" - -[ -e "$DIR/src/bitBetter/.keys" ] || mkdir "$DIR/src/bitBetter/.keys" - -cp "$DIR/.keys/cert.cert" "$DIR/src/bitBetter/.keys" - -docker run --rm -v "$DIR/src/bitBetter:/bitBetter" -w=/bitBetter mcr.microsoft.com/dotnet/sdk:6.0 sh build.sh - -docker build --no-cache --build-arg BITWARDEN_TAG=bitwarden/api:$BW_VERSION --label com.bitwarden.product="bitbetter" -t bitbetter/api "$DIR/src/bitBetter" # --squash -docker build --no-cache --build-arg BITWARDEN_TAG=bitwarden/identity:$BW_VERSION --label com.bitwarden.product="bitbetter" -t bitbetter/identity "$DIR/src/bitBetter" # --squash - -docker tag bitbetter/api bitbetter/api:latest -docker tag bitbetter/identity bitbetter/identity:latest -docker tag bitbetter/api bitbetter/api:$BW_VERSION -docker tag bitbetter/identity bitbetter/identity:$BW_VERSION - -# Remove old instances of the image after a successful build. -ids=$( docker images bitbetter/* | grep -E -v -- "CREATED|latest|${BW_VERSION}" | awk '{ print $3 }' ) -[ -n "$ids" ] && docker rmi $ids || true diff --git a/generateKeys.ps1 b/generateKeys.ps1 new file mode 100644 index 0000000..3e65d03 --- /dev/null +++ b/generateKeys.ps1 @@ -0,0 +1,16 @@ +$opensslbinary = "$Env:Programfiles\OpenSSL-Win64\bin\openssl.exe" + +if (!(Get-Command $opensslbinary -errorAction SilentlyContinue)) +{ + winget install openssl +} + +if (Test-Path "$pwd\.keys") +{ + Remove-Item "$pwd\.keys" -Recurse -Force +} +New-item -ItemType Directory -Path "$pwd\.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' pkcs12 -export -out `"$pwd\.keys\cert.pfx`" -inkey `"$pwd\.keys\key.pem`" -in `"$pwd\.keys\cert.pem`" -passin pass:test -passout pass:test" \ No newline at end of file diff --git a/licenseGen.ps1 b/licenseGen.ps1 new file mode 100644 index 0000000..e3a6065 --- /dev/null +++ b/licenseGen.ps1 @@ -0,0 +1,15 @@ +if ($($args.Count) -lt 1) { + echo "USAGE: [License Gen args...]" + echo "ACTIONS:" + echo " interactive" + echo " user" + echo " org" + Exit 1 +} + +if ($args[0] = "interactive") { + $shiftedarray = $args[1 .. ($args.count-1)] + docker run -it --rm bitbetter/licensegen "$shiftedarray" +} else { + docker run bitbetter/licensegen "$shiftedarray" +} diff --git a/licenseGen.sh b/licenseGen.sh new file mode 100644 index 0000000..698bf20 --- /dev/null +++ b/licenseGen.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +DIR=`dirname "$0"` +DIR=`exec 2>/dev/null;(cd -- "$DIR") && cd -- "$DIR"|| cd "$DIR"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd` + +if [ "$#" -lt "2" ]; then + echo "USAGE: [License Gen args...]" + echo "ACTIONS:" + echo " interactive" + echo " user" + echo " org" + exit 1 +fi + +if [ "$1" = "interactive" ]; then + shift + docker run -it --rm bitbetter/licensegen "$@" +else + docker run --rm bitbetter/licensegen "$@" +fi diff --git a/licenseGen/Dockerfile b/licenseGen/Dockerfile new file mode 100644 index 0000000..f23d3fd --- /dev/null +++ b/licenseGen/Dockerfile @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/dotnet/sdk:6.0 as build +WORKDIR /licenseGen + +COPY . /licenseGen +COPY Core.dll /app/ +COPY cert.pfx /app/ + +RUN dotnet restore +RUN dotnet publish -c Release -o /app --no-restore + +FROM mcr.microsoft.com/dotnet/sdk:6.0 +WORKDIR /app +COPY --from=build /app . + +ENTRYPOINT [ "dotnet", "/app/licenseGen.dll", "--core", "/app/Core.dll", "--cert", "/app/cert.pfx" ] diff --git a/licenseGen/Program.cs b/licenseGen/Program.cs new file mode 100644 index 0000000..57e7a32 --- /dev/null +++ b/licenseGen/Program.cs @@ -0,0 +1,445 @@ +using System; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Extensions.CommandLineUtils; +using Newtonsoft.Json; + +namespace licenseGen; + +internal class Program +{ + 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 => + { + String buff, licensetype="", name="", email="", businessname=""; + Int16 storage = 0; + + Boolean validGuid = false, validInstallid = false; + Guid guid = new(), installid = new(); + + config.OnExecute(() => + { + if (!VerifyTopOptions()) + { + if (!CoreExists()) config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); + if (!CertExists()) config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); + + config.ShowHelp(); + return 1; + } + + WriteLine("Interactive license mode..."); + + while (licensetype == "") + { + WriteLine("What would you like to generate, a [u]ser license or an [o]rg license?"); + buff = Console.ReadLine(); + + if(buff == "u") + { + licensetype = "user"; + WriteLineOver("Okay, we will generate a user license."); + + while (validGuid == false) + { + WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]:"); + buff = Console.ReadLine(); + + if (Guid.TryParse(buff, out guid))validGuid = true; + else WriteLineOver("The user-guid provided does not appear to be valid."); + } + } + else if (buff == "o") + { + licensetype = "org"; + WriteLineOver("Okay, we will generate an organization license."); + + while (validInstallid == false) + { + WriteLine("Please provide your Bitwarden Install-ID — refer to the Readme for details on how to retrieve this. [Install-ID]:"); + buff = Console.ReadLine(); + + if (Guid.TryParse(buff, out installid)) validInstallid = true; + else WriteLineOver("The install-id provided does not appear to be valid."); + } + + while (businessname == "") + { + WriteLineOver("Please enter a business name, default is BitBetter. [Business Name]:"); + buff = Console.ReadLine(); + if (buff == "") businessname = "BitBetter"; + else if (CheckBusinessName(buff)) businessname = buff; + } + } + else + { + WriteLineOver("Unrecognized option \'" + buff + "\'. "); + } + } + + while (name == "") + { + WriteLineOver("Please provide the username this license will be registered to. [username]:"); + buff = Console.ReadLine(); + if ( CheckUsername(buff) ) name = buff; + } + + while (email == "") + { + WriteLineOver("Please provide the email address for the user " + name + ". [email]"); + buff = Console.ReadLine(); + if ( CheckEmail(buff) ) email = buff; + } + + while (storage == 0) + { + WriteLineOver("Extra storage space for the user " + name + ". (max.: " + Int16.MaxValue + "). Defaults to maximum value. [storage]"); + buff = Console.ReadLine(); + if (String.IsNullOrWhiteSpace(buff)) + { + storage = Int16.MaxValue; + } + else + { + if (CheckStorage(buff)) storage = Int16.Parse(buff); + } + } + + if (licensetype == "user") + { + WriteLineOver("Confirm creation of \"user\" license for username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", User-GUID: \"" + guid + "\"? Y/n"); + buff = Console.ReadLine(); + if ( buff == "" || buff == "y" || buff == "Y" ) + { + GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name, email, storage, guid, null); + } + else + { + WriteLineOver("Exiting..."); + return 0; + } + } + else if (licensetype == "org") + { + WriteLineOver("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 == "" || buff == "y" || buff == "Y" ) + { + GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name, email, storage, installid, businessname, null); + } + else + { + WriteLineOver("Exiting..."); + return 0; + } + } + + return 0; + }); + }); + + app.Command("user", config => + { + CommandArgument name = config.Argument("Name", "your name"); + CommandArgument email = config.Argument("Email", "your email"); + CommandArgument userIdArg = config.Argument("User ID", "your user id"); + CommandArgument storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + Int16.MaxValue + " (optional, default = max)"); + CommandArgument key = config.Argument("Key", "your key id (optional)"); + + config.OnExecute(() => + { + if (!VerifyTopOptions()) + { + if (!CoreExists()) + { + config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); + } + if (!CertExists()) + { + config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); + } + + config.ShowHelp(); + return 1; + } + else if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value)) + { + config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}'"); + config.ShowHelp("user"); + return 1; + } + + if (String.IsNullOrWhiteSpace(userIdArg.Value) || !Guid.TryParse(userIdArg.Value, out Guid userId)) + { + config.Error.WriteLine("User ID not provided"); + config.ShowHelp("user"); + return 1; + } + + Int16 storageShort = 0; + if (!String.IsNullOrWhiteSpace(storage.Value)) + { + Double parsedStorage = Double.Parse(storage.Value); + if (parsedStorage > Int16.MaxValue || parsedStorage < 0) + { + config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]"); + config.ShowHelp("org"); + return 1; + } + storageShort = (Int16) parsedStorage; + } + + GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value); + + return 0; + }); + }); + app.Command("org", config => + { + CommandArgument name = config.Argument("Name", "your name"); + CommandArgument email = config.Argument("Email", "your email"); + CommandArgument installId = config.Argument("InstallId", "your installation id (GUID)"); + CommandArgument storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + Int16.MaxValue + " (optional, default = max)"); + CommandArgument businessName = config.Argument("BusinessName", "name for the organization (optional)"); + CommandArgument key = config.Argument("Key", "your key id (optional)"); + + config.OnExecute(() => + { + if (!VerifyTopOptions()) + { + if (!CoreExists()) + { + config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); + } + if (!CertExists()) + { + config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); + } + + config.ShowHelp(); + return 1; + } + else 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("org"); + return 1; + } + + if (!Guid.TryParse(installId.Value, out Guid installationId)) + { + config.Error.WriteLine("Unable to parse your installation id as a GUID"); + config.Error.WriteLine($"Here's a new guid: {Guid.NewGuid()}"); + config.ShowHelp("org"); + return 1; + } + + Int16 storageShort = 0; + if (!String.IsNullOrWhiteSpace(storage.Value)) + { + Double parsedStorage = Double.Parse(storage.Value); + if (parsedStorage > Int16.MaxValue || parsedStorage < 0) + { + config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]"); + config.ShowHelp("org"); + return 1; + } + storageShort = (Int16) parsedStorage; + } + + GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value); + + return 0; + }); + }); + + app.OnExecute(() => + { + app.ShowHelp(); + return 10; + }); + + app.HelpOption("-? | -h | --help"); + + try + { + return app.Execute(args); + } + catch (Exception e) + { + Console.Error.WriteLine("Oops: {0}", e); + return 100; + } + } + + // checkUsername Checks that the username is a valid username + private static Boolean CheckUsername(String s) + { + if ( String.IsNullOrWhiteSpace(s) ) { + WriteLineOver("The username provided doesn't appear to be valid.\n"); + return false; + } + return true; // TODO: Actually validate + } + + // checkBusinessName Checks that the Business Name is a valid username + private static Boolean CheckBusinessName(String s) + { + if ( String.IsNullOrWhiteSpace(s) ) { + WriteLineOver("The Business Name provided doesn't appear to be valid.\n"); + return false; + } + return true; // TODO: Actually validate + } + + // checkEmail Checks that the email address is a valid email address + private static Boolean CheckEmail(String s) + { + if ( String.IsNullOrWhiteSpace(s) ) { + WriteLineOver("The email provided doesn't appear to be valid.\n"); + return false; + } + return true; // TODO: Actually validate + } + + // checkStorage Checks that the storage is in a valid range + private static Boolean CheckStorage(String s) + { + if (String.IsNullOrWhiteSpace(s)) + { + WriteLineOver("The storage provided doesn't appear to be valid.\n"); + return false; + } + if (Double.Parse(s) > Int16.MaxValue || Double.Parse(s) < 0) + { + WriteLineOver("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "].\n"); + return false; + } + return true; + } + + // WriteLineOver Writes a new line to console over last line. + private static void WriteLineOver(String s) + { + Console.SetCursorPosition(0, Console.CursorTop -1); + Console.WriteLine(s); + } + + // WriteLine This wrapper is just here so that console writes all look similar. + private static void WriteLine(String s) + { + Console.WriteLine(s); + } + + private static void GenerateUserLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid userId, String key) + { + Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); + + Type type = core.GetType("Bit.Core.Models.Business.UserLicense"); + Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); + + Object license = Activator.CreateInstance(type); + + void Set(String name, Object value) + { + type.GetProperty(name).SetValue(license, value); + } + + 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, new Object[0]))); + Set("Signature", Convert.ToBase64String((Byte[])type.GetMethod("Sign").Invoke(license, new Object[] { cert }))); + + Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); + } + + 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); + + Type type = core.GetType("Bit.Core.Models.Business.OrganizationLicense"); + Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); + Type planTypeEnum = core.GetType("Bit.Core.Enums.PlanType"); + + Object license = Activator.CreateInstance(type); + + void set(String name, Object value) + { + type.GetProperty(name).SetValue(license, value); + } + + 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", "Custom"); + set("PlanType", Enum.Parse(planTypeEnum, "Custom")); + set("Seats", (Int32)Int16.MaxValue); + set("MaxCollections", Int16.MaxValue); + set("UsePolicies", true); + set("UseSso", true); + set("UseKeyConnector", true); + //set("UseScim", true); // available in version 10, which is not released yet + set("UseGroups", true); + set("UseEvents", true); + set("UseDirectory", true); + set("UseTotp", true); + set("Use2fa", true); + set("UseApi", true); + set("UseResetPassword", true); + set("MaxStorageGb", storage == 0 ? Int16.MaxValue : storage); + set("SelfHost", true); + set("UsersGetPremium", true); + set("Version", 9); + 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("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 }))); + + Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); + } +} diff --git a/src/licenseGen/licenseGen.csproj b/licenseGen/licenseGen.csproj similarity index 85% rename from src/licenseGen/licenseGen.csproj rename to licenseGen/licenseGen.csproj index 0edfd9a..93611d5 100644 --- a/src/licenseGen/licenseGen.csproj +++ b/licenseGen/licenseGen.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/bitBetter/Dockerfile b/src/bitBetter/Dockerfile deleted file mode 100644 index 07bf05c..0000000 --- a/src/bitBetter/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -ARG BITWARDEN_TAG -FROM ${BITWARDEN_TAG} - -COPY bin/Debug/netcoreapp6.0/publish/* /bitBetter/ -COPY ./.keys/cert.cert /newLicensing.cer - -RUN set -e; set -x; \ - dotnet /bitBetter/bitBetter.dll && \ - mv /app/Core.dll /app/Core.orig.dll && \ - mv /app/modified.dll /app/Core.dll && \ - rm -rf /bitBetter && rm -rf /newLicensing.cer diff --git a/src/bitBetter/Program.cs b/src/bitBetter/Program.cs deleted file mode 100644 index 0c7b93e..0000000 --- a/src/bitBetter/Program.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Cecil.Rocks; - -namespace bitwardenSelfLicensor -{ - class Program - { - static int Main(string[] args) - { - string cerFile; - string corePath; - - if(args.Length >= 2) { - cerFile = args[0]; - corePath = args[1]; - } else if (args.Length == 1) { - cerFile = args[0]; - corePath = "/app/Core.dll"; - } - else { - cerFile = "/newLicensing.cer"; - corePath = "/app/Core.dll"; - } - - - var module = ModuleDefinition.ReadModule(new MemoryStream(File.ReadAllBytes(corePath))); - var cert = File.ReadAllBytes(cerFile); - - var x = module.Resources.OfType() - .Where(r => r.Name.Equals("Bit.Core.licensing.cer")) - .First(); - - Console.WriteLine(x.Name); - - var e = new EmbeddedResource("Bit.Core.licensing.cer", x.Attributes, cert); - - module.Resources.Add(e); - module.Resources.Remove(x); - - var services = module.Types.Where(t => t.Namespace == "Bit.Core.Services"); - - - var type = services.First(t => t.Name == "LicensingService"); - - var licensingType = type.Resolve(); - - var existingCert = new X509Certificate2(x.GetResourceData()); - - Console.WriteLine($"Existing Cert Thumbprint: {existingCert.Thumbprint}"); - X509Certificate2 certificate = new X509Certificate2(cert); - - Console.WriteLine($"New Cert Thumbprint: {certificate.Thumbprint}"); - - var ctor = licensingType.GetConstructors().Single(); - - - var rewriter = ctor.Body.GetILProcessor(); - - var instToReplace = - ctor.Body.Instructions.Where(i => i.OpCode == OpCodes.Ldstr - && string.Equals((string)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase)) - .FirstOrDefault(); - - if(instToReplace != null) { - rewriter.Replace(instToReplace, Instruction.Create(OpCodes.Ldstr, certificate.Thumbprint)); - } - else { - Console.WriteLine("Cant find inst"); - } - - // foreach (var inst in ctor.Body.Instructions) - // { - // Console.Write(inst.OpCode.Name + " " + inst.Operand?.GetType() + " = "); - // if(inst.OpCode.FlowControl == FlowControl.Call) { - // Console.WriteLine(inst.Operand); - // } - // else if(inst.OpCode == OpCodes.Ldstr) { - // Console.WriteLine(inst.Operand); - // } - // else {Console.WriteLine();} - // } - - module.Write("modified.dll"); - - return 0; - } - } -} diff --git a/src/bitBetter/build.sh b/src/bitBetter/build.sh deleted file mode 100755 index ea95081..0000000 --- a/src/bitBetter/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -set -e -set -x - -dotnet restore -dotnet publish diff --git a/src/licenseGen/Dockerfile b/src/licenseGen/Dockerfile deleted file mode 100644 index 2ca6a37..0000000 --- a/src/licenseGen/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 as build - -WORKDIR /licenseGen - -COPY . /licenseGen - -RUN set -e; set -x; \ - dotnet add package Newtonsoft.Json --version 13.0.1 \ - && dotnet restore \ - && dotnet publish - - -FROM bitbetter/api - -COPY --from=build /licenseGen/bin/Debug/netcoreapp6.0/publish/* /app/ - -ENTRYPOINT [ "dotnet", "/app/licenseGen.dll", "--core", "/app/Core.dll", "--cert", "/cert.pfx" ] diff --git a/src/licenseGen/Program.cs b/src/licenseGen/Program.cs deleted file mode 100644 index db395f0..0000000 --- a/src/licenseGen/Program.cs +++ /dev/null @@ -1,448 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Runtime.Loader; -using System.Security.Cryptography.X509Certificates; -using Microsoft.Extensions.CommandLineUtils; -using Newtonsoft.Json; - -namespace bitwardenSelfLicensor -{ - class Program - { - static int Main(string[] args) - { - var app = new Microsoft.Extensions.CommandLineUtils.CommandLineApplication(); - var cert = app.Option("--cert", "cert file", CommandOptionType.SingleValue); - var coreDll = app.Option("--core", "path to core dll", CommandOptionType.SingleValue); - - bool certExists() - { - return File.Exists(cert.Value()); - } - - bool coreExists() - { - return File.Exists(coreDll.Value()); - } - - bool verifyTopOptions() - { - return !string.IsNullOrWhiteSpace(cert.Value()) && - !string.IsNullOrWhiteSpace(coreDll.Value()) && - certExists() && coreExists(); - } - - app.Command("interactive", config => - { - string buff="", licensetype="", name="", email="", businessname=""; - short storage = 0; - - bool valid_guid = false, valid_installid = false; - Guid guid = new Guid(), installid = new Guid(); - - config.OnExecute(() => - { - if (!verifyTopOptions()) - { - if (!coreExists()) config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); - if (!certExists()) config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); - - config.ShowHelp(); - return 1; - } - - WriteLine("Interactive license mode..."); - - while (licensetype == "") - { - WriteLine("What would you like to generate, a [u]ser license or an [o]rg license?"); - buff = Console.ReadLine(); - - if(buff == "u") - { - licensetype = "user"; - WriteLineOver("Okay, we will generate a user license."); - - while (valid_guid == false) - { - WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]:"); - buff = Console.ReadLine(); - - if (Guid.TryParse(buff, out guid))valid_guid = true; - else WriteLineOver("The user-guid provided does not appear to be valid."); - } - } - else if (buff == "o") - { - licensetype = "org"; - WriteLineOver("Okay, we will generate an organization license."); - - while (valid_installid == false) - { - WriteLine("Please provide your Bitwarden Install-ID — refer to the Readme for details on how to retrieve this. [Install-ID]:"); - buff = Console.ReadLine(); - - if (Guid.TryParse(buff, out installid)) valid_installid = true; - else WriteLineOver("The install-id provided does not appear to be valid."); - } - - while (businessname == "") - { - WriteLineOver("Please enter a business name, default is BitBetter. [Business Name]:"); - buff = Console.ReadLine(); - if (buff == "") businessname = "BitBetter"; - else if (checkBusinessName(buff)) businessname = buff; - } - } - else - { - WriteLineOver("Unrecognized option \'" + buff + "\'. "); - } - } - - while (name == "") - { - WriteLineOver("Please provide the username this license will be registered to. [username]:"); - buff = Console.ReadLine(); - if ( checkUsername(buff) ) name = buff; - } - - while (email == "") - { - WriteLineOver("Please provide the email address for the user " + name + ". [email]"); - buff = Console.ReadLine(); - if ( checkEmail(buff) ) email = buff; - } - - while (storage == 0) - { - WriteLineOver("Extra storage space for the user " + name + ". (max.: " + short.MaxValue + "). Defaults to maximum value. [storage]"); - buff = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(buff)) - { - storage = short.MaxValue; - } - else - { - if (checkStorage(buff)) storage = short.Parse(buff); - } - } - - if (licensetype == "user") - { - WriteLineOver("Confirm creation of \"user\" license for username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", User-GUID: \"" + guid + "\"? Y/n"); - buff = Console.ReadLine(); - if ( buff == "" || buff == "y" || buff == "Y" ) - { - GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name, email, storage, guid, null); - } - else - { - WriteLineOver("Exiting..."); - return 0; - } - } - else if (licensetype == "org") - { - WriteLineOver("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 == "" || buff == "y" || buff == "Y" ) - { - GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name, email, storage, installid, businessname, null); - } - else - { - WriteLineOver("Exiting..."); - return 0; - } - } - - return 0; - }); - }); - - app.Command("user", config => - { - var name = config.Argument("Name", "your name"); - var email = config.Argument("Email", "your email"); - var userIdArg = config.Argument("User ID", "your user id"); - var storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + short.MaxValue + " (optional, default = max)"); - var key = config.Argument("Key", "your key id (optional)"); - var help = config.HelpOption("--help | -h | -?"); - - config.OnExecute(() => - { - if (!verifyTopOptions()) - { - if (!coreExists()) - { - config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); - } - if (!certExists()) - { - config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); - } - - config.ShowHelp(); - return 1; - } - else if (string.IsNullOrWhiteSpace(name.Value) || string.IsNullOrWhiteSpace(email.Value)) - { - config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}'"); - config.ShowHelp("user"); - return 1; - } - - if (string.IsNullOrWhiteSpace(userIdArg.Value) || !Guid.TryParse(userIdArg.Value, out Guid userId)) - { - config.Error.WriteLine($"User ID not provided"); - config.ShowHelp("user"); - return 1; - } - - short storageShort = 0; - if (!string.IsNullOrWhiteSpace(storage.Value)) - { - var parsedStorage = double.Parse(storage.Value); - if (parsedStorage > short.MaxValue || parsedStorage < 0) - { - config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + short.MaxValue + "]"); - config.ShowHelp("org"); - return 1; - } - storageShort = (short) parsedStorage; - } - - GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value); - - return 0; - }); - }); - app.Command("org", config => - { - var name = config.Argument("Name", "your name"); - var email = config.Argument("Email", "your email"); - var installId = config.Argument("InstallId", "your installation id (GUID)"); - var storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + short.MaxValue + " (optional, default = max)"); - var businessName = config.Argument("BusinessName", "name for the organization (optional)"); - var key = config.Argument("Key", "your key id (optional)"); - var help = config.HelpOption("--help | -h | -?"); - - config.OnExecute(() => - { - if (!verifyTopOptions()) - { - if (!coreExists()) - { - config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); - } - if (!certExists()) - { - config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); - } - - config.ShowHelp(); - return 1; - } - else 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("org"); - return 1; - } - - if (!Guid.TryParse(installId.Value, out Guid installationId)) - { - config.Error.WriteLine("Unable to parse your installation id as a GUID"); - config.Error.WriteLine($"Here's a new guid: {Guid.NewGuid()}"); - config.ShowHelp("org"); - return 1; - } - - short storageShort = 0; - if (!string.IsNullOrWhiteSpace(storage.Value)) - { - var parsedStorage = double.Parse(storage.Value); - if (parsedStorage > short.MaxValue || parsedStorage < 0) - { - config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + short.MaxValue + "]"); - config.ShowHelp("org"); - return 1; - } - storageShort = (short) parsedStorage; - } - - GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value); - - return 0; - }); - }); - - app.OnExecute(() => - { - app.ShowHelp(); - return 10; - }); - - app.HelpOption("-? | -h | --help"); - - try - { - return app.Execute(args); - } - catch (Exception e) - { - Console.Error.WriteLine("Oops: {0}", e); - return 100; - } - } - - // checkUsername Checks that the username is a valid username - static bool checkUsername(string s) - { - if ( string.IsNullOrWhiteSpace(s) ) { - WriteLineOver("The username provided doesn't appear to be valid.\n"); - return false; - } - return true; // TODO: Actually validate - } - - // checkBusinessName Checks that the Business Name is a valid username - static bool checkBusinessName(string s) - { - if ( string.IsNullOrWhiteSpace(s) ) { - WriteLineOver("The Business Name provided doesn't appear to be valid.\n"); - return false; - } - return true; // TODO: Actually validate - } - - // checkEmail Checks that the email address is a valid email address - static bool checkEmail(string s) - { - if ( string.IsNullOrWhiteSpace(s) ) { - WriteLineOver("The email provided doesn't appear to be valid.\n"); - return false; - } - return true; // TODO: Actually validate - } - - // checkStorage Checks that the storage is in a valid range - static bool checkStorage(string s) - { - if (string.IsNullOrWhiteSpace(s)) - { - WriteLineOver("The storage provided doesn't appear to be valid.\n"); - return false; - } - if (double.Parse(s) > short.MaxValue || double.Parse(s) < 0) - { - WriteLineOver("The storage value provided is outside the accepted range of [0-" + short.MaxValue + "].\n"); - return false; - } - return true; - } - - // WriteLineOver Writes a new line to console over last line. - static void WriteLineOver(string s) - { - Console.SetCursorPosition(0, Console.CursorTop -1); - Console.WriteLine(s); - } - - // WriteLine This wrapper is just here so that console writes all look similar. - static void WriteLine(string s) - { - Console.WriteLine(s); - } - - static void GenerateUserLicense(X509Certificate2 cert, string corePath, string userName, string email, short storage, Guid userId, string key) - { - var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); - - var type = core.GetType("Bit.Core.Models.Business.UserLicense"); - var licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); - - var license = Activator.CreateInstance(type); - - void set(string name, object value) - { - type.GetProperty(name).SetValue(license, value); - } - - 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 ? short.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, new object[0]))); - set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert }))); - - Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); - } - - static void GenerateOrgLicense(X509Certificate2 cert, string corePath, string userName, string email, short storage, Guid instalId, string businessName, string key) - { - var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); - - var type = core.GetType("Bit.Core.Models.Business.OrganizationLicense"); - var licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); - var planTypeEnum = core.GetType("Bit.Core.Enums.PlanType"); - - var license = Activator.CreateInstance(type); - - void set(string name, object value) - { - type.GetProperty(name).SetValue(license, value); - } - - 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", "Custom"); - set("PlanType", Enum.Parse(planTypeEnum, "Custom")); - set("Seats", (int)short.MaxValue); - set("MaxCollections", short.MaxValue); - set("UsePolicies", true); - set("UseSso", true); - set("UseKeyConnector", true); - //set("UseScim", true); // available in version 10, which is not released yet - set("UseGroups", true); - set("UseEvents", true); - set("UseDirectory", true); - set("UseTotp", true); - set("Use2fa", true); - set("UseApi", true); - set("UseResetPassword", true); - set("MaxStorageGb", storage == 0 ? short.MaxValue : storage); - set("SelfHost", true); - set("UsersGetPremium", true); - set("Version", 9); - 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("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 }))); - - Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); - } - } -} diff --git a/src/licenseGen/build.sh b/src/licenseGen/build.sh deleted file mode 100755 index 31d08ac..0000000 --- a/src/licenseGen/build.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -DIR=`dirname "$0"` -DIR=`exec 2>/dev/null;(cd -- "$DIR") && cd -- "$DIR"|| cd "$DIR"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd` - -docker build -t bitbetter/licensegen "$DIR" # --squash diff --git a/src/licenseGen/run.sh b/src/licenseGen/run.sh deleted file mode 100755 index ba504c2..0000000 --- a/src/licenseGen/run.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -DIR=`dirname "$0"` -DIR=`exec 2>/dev/null;(cd -- "$DIR") && cd -- "$DIR"|| cd "$DIR"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd` - -# Grab the absolute path to the default pfx location -cert_path="$DIR/../../.keys/cert.pfx" - -if [ "$#" -lt "2" ]; then - echo "USAGE: $0 [License Gen args...]" - echo "ACTIONS:" - echo " interactive" - echo " user" - echo " org" - exit 1 -fi - -cert_path="$1" -action="$2" -shift - -if [ $action = "interactive" ]; then - docker run -it --rm -v "$cert_path:/cert.pfx" bitbetter/licensegen "$@" -else - docker run --rm -v "$cert_path:/cert.pfx" bitbetter/licensegen "$@" -fi diff --git a/update-bitwarden.sh b/update-bitwarden.sh deleted file mode 100755 index 069f983..0000000 --- a/update-bitwarden.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/bash -ask () { - local __resultVar=$1 - local __result="$2" - if [ -z "$2" ]; then - read -p "$3" __result - fi - eval $__resultVar="'$__result'" -} - -SCRIPT_BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -BW_VERSION=$(curl -sL https://go.btwrdn.co/bw-sh-versions | grep '^ *"'coreVersion'":' | awk -F\: '{ print $2 }' | sed -e 's/,$//' -e 's/^"//' -e 's/"$//') - -echo "Starting Bitwarden update, newest server version: $BW_VERSION" - -# Default path is the parent directory of the BitBetter location -BITWARDEN_BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/dev/null 2>&1 && pwd )" - -# Get Bitwarden base from user (or keep default value) or use first argument -ask tmpbase "$1" "Enter Bitwarden base directory [$BITWARDEN_BASE]: " -BITWARDEN_BASE=${tmpbase:-$BITWARDEN_BASE} - -# Check if directory exists and is valid -[ -d "$BITWARDEN_BASE" ] || { echo "Bitwarden base directory $BITWARDEN_BASE not found!"; exit 1; } -[ -f "$BITWARDEN_BASE/bitwarden.sh" ] || { echo "Bitwarden base directory $BITWARDEN_BASE is not valid!"; exit 1; } - -# Check if user wants to recreate the docker-compose override file -RECREATE_OV="y" -ask tmprecreate "$2" "Rebuild docker-compose override? [Y/n]: " -RECREATE_OV=${tmprecreate:-$RECREATE_OV} - -if [[ $RECREATE_OV =~ ^[Yy]$ ]] -then - { - echo "version: '3'" - echo "" - echo "services:" - echo " api:" - echo " image: bitbetter/api:$BW_VERSION" - echo "" - echo " identity:" - echo " image: bitbetter/identity:$BW_VERSION" - echo "" - } > $BITWARDEN_BASE/bwdata/docker/docker-compose.override.yml - echo "BitBetter docker-compose override created!" -else - echo "Make sure to check if the docker override contains the correct image version ($BW_VERSION) in $BITWARDEN_BASE/bwdata/docker/docker-compose.override.yml!" -fi - -# Check if user wants to rebuild the bitbetter images -docker images bitbetter/api --format="{{ .Tag }}" | grep -F -- "${BW_VERSION}" > /dev/null -retval=$? -REBUILD_BB="n" -REBUILD_BB_DESCR="[y/N]" -if [ $retval -ne 0 ]; then - REBUILD_BB="y" - REBUILD_BB_DESCR="[Y/n]" -fi -ask tmprebuild "$3" "Rebuild BitBetter images? $REBUILD_BB_DESCR: " -REBUILD_BB=${tmprebuild:-$REBUILD_BB} - -if [[ $REBUILD_BB =~ ^[Yy]$ ]] -then - $SCRIPT_BASE/build.sh - echo "BitBetter images updated to version: $BW_VERSION" -fi - -# Now start the bitwarden update -cd $BITWARDEN_BASE - -./bitwarden.sh updateself - -# Update the bitwarden.sh: automatically patch run.sh to fix docker-compose pull errors for private images -awk '1;/function downloadRunFile/{c=6}c&&!--c{print "sed -i '\''s/dccmd pull/dccmd pull --ignore-pull-failures || true/g'\'' $SCRIPTS_DIR/run.sh"}' $BITWARDEN_BASE/bitwarden.sh > tmp_bw.sh && mv tmp_bw.sh $BITWARDEN_BASE/bitwarden.sh -chmod +x $BITWARDEN_BASE/bitwarden.sh -echo "Patching bitwarden.sh completed..." - -./bitwarden.sh update - -# Prune Docker images without at least one container associated to them. -echo "Pruning Docker images without at least one container associated to them..." -docker image prune -a - -cd $SCRIPT_BASE -echo "Bitwarden update completed!"