mirror of
				https://github.com/jakeswenson/BitBetter.git
				synced 2025-10-31 04:43:25 +00:00 
			
		
		
		
	* Initial work * Fix typo * Fix typo * Fix stupid issue * Add comments and fix minor issues * Add extra information * Add Linux script for generating keys * Add circleci * Add comments * Add extra option * Add missing permissions and empty script for now * Fix line endings * Add missing mount point * Simplify patch * Fix scripts * Reduce complexity * Fix circleci * Remove useless line * Move to src folder and improve image creation
This commit is contained in:
		
							parent
							
								
									b819fe0c7d
								
							
						
					
					
						commit
						d4abc9e5b7
					
				|  | @ -1,4 +1,4 @@ | ||||||
| version: 2 | version: 2.1 | ||||||
| jobs: | jobs: | ||||||
|   build: |   build: | ||||||
|     machine: true |     machine: true | ||||||
|  | @ -9,7 +9,7 @@ jobs: | ||||||
|           command: date |           command: date | ||||||
|       - run: |       - run: | ||||||
|           name: Generate Keys |           name: Generate Keys | ||||||
|           command: ./.keys/generate-keys.sh |           command: ./generateKeys.sh | ||||||
|       - run: |       - run: | ||||||
|           name: Build script |           name: Build script | ||||||
|           command: ./build.sh |           command: ./build.sh y | ||||||
							
								
								
									
										3
									
								
								.servers/serverlist.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.servers/serverlist.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | 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 | ||||||
|  | <OR> | ||||||
|  | docker-compose -f <full-local-path>/docker-compose.yml up -d | ||||||
							
								
								
									
										134
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								README.md
									
									
									
									
									
								
							|  | @ -1,10 +1,10 @@ | ||||||
| # BitBetter | # 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. | 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 | Credit to https://github.com/h44z/BitBetter and https://github.com/jakeswenson/BitBetter | ||||||
| 
 | 
 | ||||||
|  | @ -25,55 +25,70 @@ Credit to https://github.com/h44z/BitBetter and https://github.com/jakeswenson/B | ||||||
| - [Footnotes](#footnotes) | - [Footnotes](#footnotes) | ||||||
| 
 | 
 | ||||||
| # Getting Started | # 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 | ## Dependencies | ||||||
| Aside from docker, which you also need for Bitwarden, BitBetter requires the following: | Aside from docker, which you also need for Bitwarden, BitBetter requires the following: | ||||||
| 
 | 
 | ||||||
| * Bitwarden (tested with 1.47.1, might work on lower versions) | * 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 | ## Setting up BitBetter | ||||||
| With your dependencies installed, begin the installation of BitBetter by downloading it through Github or using the git command: | 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 | git clone https://github.com/jakeswenson/BitBetter.git | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Building BitBetter | ## 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. | ||||||
|  | This file may be empty, but there will be no containers will be spun up automatically. | ||||||
|  | 
 | ||||||
|  | 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: | 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`<br>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 | ### Note: Manually generating Certificate & Key | ||||||
| 
 | 
 | ||||||
| If you wish to generate your self-signed cert & key manually, you can run the following commands. | 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 | ```bash | ||||||
| openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cert -days 36500 -outform DER -passout pass:test | openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cert -days 36500 -outform DER -passout pass:test | ||||||
|  | @ -81,69 +96,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 | 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`.<sup>[1](#f1)</sup> | > Note that the password here must be `test`.<sup>[2](#f1)</sup> | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| ## 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**.<sup>[2](#f2)</sup> |  | ||||||
| 
 |  | ||||||
| ```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. | # FAQ: Questions you might have. | ||||||
| 
 | 
 | ||||||
| ## Why build a license generator for open source software? | ## Why build a license generator for open source software? | ||||||
|  | @ -161,7 +118,6 @@ UPDATE: Bitwarden now offers a cheap license called [Families Organization](http | ||||||
| 
 | 
 | ||||||
| # Footnotes | # Footnotes | ||||||
| 
 | 
 | ||||||
| <a name="#f1"><sup>1</sup></a> 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. | <a name="#f1"><sup>1</sup></a>This tool builds on top of the `bitbetter/api` container image so make sure you've built that above using the root `./build.sh` script. | ||||||
| 
 |  | ||||||
| <a name="#f2"><sup>2</sup></a>This tool builds on top of the `bitbetter/api` container image so make sure you've built that above using the root `./build.sh` script. |  | ||||||
| 
 | 
 | ||||||
|  | <a name="#f2"><sup>2</sup></a> 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. | ||||||
|  |  | ||||||
							
								
								
									
										104
									
								
								build.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								build.ps1
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,104 @@ | ||||||
|  | # define temporary directory | ||||||
|  | $tempdirectory = "$pwd\temp" | ||||||
|  | # define services to patch | ||||||
|  | $components = "Api","Identity" | ||||||
|  | 
 | ||||||
|  | # delete old directories / files if applicable | ||||||
|  | if (Test-Path "$tempdirectory") { | ||||||
|  | 	Remove-Item "$tempdirectory" -Recurse -Force | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if (Test-Path -Path "$pwd\src\licenseGen\Core.dll" -PathType Leaf) { | ||||||
|  | 	Remove-Item "$pwd\src\licenseGen\Core.dll" -Force | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # generate keys if none are available | ||||||
|  | if (!(Test-Path "$pwd\.keys")) { | ||||||
|  | 	.\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" | ||||||
|  | 
 | ||||||
|  | # build bitBetter and clean the source directory after | ||||||
|  | docker build -t bitbetter/bitbetter "$pwd\src\bitBetter" | ||||||
|  | Remove-Item "$pwd\src\bitBetter\cert.cert" -Force | ||||||
|  | 
 | ||||||
|  | # gather all running instances | ||||||
|  | $oldinstances = docker container ps --all -f Name=bitwarden --format '{{.ID}}' | ||||||
|  | 
 | ||||||
|  | # stop all running instances | ||||||
|  | foreach ($instance in $oldinstances) { | ||||||
|  | 	docker stop $instance | ||||||
|  | 	docker rm $instance | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # update bitwarden itself | ||||||
|  | if ($args[0] -eq 'y') | ||||||
|  | { | ||||||
|  | 	docker pull bitwarden/self-host:beta | ||||||
|  | } | ||||||
|  | else | ||||||
|  | { | ||||||
|  | 	$confirmation = Read-Host "Update (or get) bitwarden source container" | ||||||
|  | 	if ($confirmation -eq 'y') { | ||||||
|  | 		docker pull bitwarden/self-host:beta | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # stop and remove previous existing patch(ed) container | ||||||
|  | docker stop bitwarden-patch | ||||||
|  | docker rm bitwarden-patch | ||||||
|  | docker image rm bitwarden-patch | ||||||
|  | 
 | ||||||
|  | # start a new bitwarden instance so we can patch it | ||||||
|  | $patchinstance = docker run -d --name bitwarden-patch bitwarden/self-host:beta | ||||||
|  | 
 | ||||||
|  | # create our temporary directory | ||||||
|  | New-item -ItemType Directory -Path $tempdirectory | ||||||
|  | 
 | ||||||
|  | # extract the files that need to be patched from the services that need to be patched into our temporary directory | ||||||
|  | foreach ($component in $components) { | ||||||
|  | 	New-item -itemtype Directory -path "$tempdirectory\$component" | ||||||
|  | 	docker cp $patchinstance`:/app/$component/Core.dll "$tempdirectory\$component\Core.dll" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # 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 | ||||||
|  | 
 | ||||||
|  | # start all user requested instances | ||||||
|  | foreach($line in Get-Content "$pwd\.servers\serverlist.txt") { | ||||||
|  | 	Invoke-Expression "& $line" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # remove our bitBetter image | ||||||
|  | docker image rm bitbetter/bitbetter | ||||||
|  | 
 | ||||||
|  | # 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 | ||||||
							
								
								
									
										116
									
								
								build.sh
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								build.sh
									
									
									
									
									
								
							|  | @ -1,28 +1,106 @@ | ||||||
| #!/bin/sh | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| DIR=`dirname "$0"` | # define temporary directory | ||||||
| DIR=`exec 2>/dev/null;(cd -- "$DIR") && cd -- "$DIR"|| cd "$DIR"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd` | TEMPDIRECTORY="$PWD/temp" | ||||||
| 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" | # define services to patch | ||||||
|  | COMPONENTS=("Api" "Identity") | ||||||
| 
 | 
 | ||||||
| # If there aren't any keys, generate them first. | # delete old directories / files if applicable | ||||||
| [ -e "$DIR/.keys/cert.cert" ] || "$DIR/.keys/generate-keys.sh" | if [ -d "$TEMPDIRECTORY" ]; then | ||||||
|  | 	rm -rf "$TEMPDIRECTORY" | ||||||
|  | fi | ||||||
| 
 | 
 | ||||||
| [ -e "$DIR/src/bitBetter/.keys" ] || mkdir "$DIR/src/bitBetter/.keys" | if [ -f "$PWD/src/licenseGen/Core.dll" ]; then | ||||||
|  |     rm -f "$PWD/src/licenseGen/Core.dll" | ||||||
|  | fi | ||||||
| 
 | 
 | ||||||
| cp "$DIR/.keys/cert.cert" "$DIR/src/bitBetter/.keys" | if [ -f "$PWD/src/licenseGen/cert.pfx" ]; then | ||||||
|  |     rm -f "$PWD/src/licenseGen/cert.pfx" | ||||||
|  | fi | ||||||
| 
 | 
 | ||||||
| docker run --rm -v "$DIR/src/bitBetter:/bitBetter" -w=/bitBetter mcr.microsoft.com/dotnet/sdk:6.0 sh build.sh | if [ -f "$PWD/src/bitBetter/cert.cert" ]; then | ||||||
|  |     rm -f "$PWD/src/bitBetter/cert.cert" | ||||||
|  | fi | ||||||
| 
 | 
 | ||||||
| docker build --no-cache --build-arg BITWARDEN_TAG=bitwarden/api:$BW_VERSION --label com.bitwarden.product="bitbetter" -t bitbetter/api "$DIR/src/bitBetter" # --squash | # generate keys if none are available | ||||||
| docker build --no-cache --build-arg BITWARDEN_TAG=bitwarden/identity:$BW_VERSION --label com.bitwarden.product="bitbetter" -t bitbetter/identity "$DIR/src/bitBetter" # --squash | if [ ! -d "$PWD/.keys" ]; then | ||||||
|  | 	./generateKeys.sh | ||||||
|  | fi | ||||||
| 
 | 
 | ||||||
| docker tag bitbetter/api bitbetter/api:latest | # copy the key to bitBetter and licenseGen | ||||||
| docker tag bitbetter/identity bitbetter/identity:latest | cp -f "$PWD/.keys/cert.cert" "$PWD/src/bitBetter" | ||||||
| docker tag bitbetter/api bitbetter/api:$BW_VERSION | cp -f "$PWD/.keys/cert.pfx" "$PWD/src/licenseGen" | ||||||
| docker tag bitbetter/identity bitbetter/identity:$BW_VERSION |  | ||||||
| 
 | 
 | ||||||
| # Remove old instances of the image after a successful build. | # build bitBetter and clean the source directory after | ||||||
| ids=$( docker images bitbetter/* | grep -E -v -- "CREATED|latest|${BW_VERSION}" | awk '{ print $3 }' ) | docker build -t bitbetter/bitbetter "$PWD/src/bitBetter" | ||||||
| [ -n "$ids" ] && docker rmi $ids || true | rm -f "$PWD/src/bitBetter/cert.cert" | ||||||
|  | 
 | ||||||
|  | # gather all running instances | ||||||
|  | OLDINSTANCES=$(docker container ps --all -f Name=bitwarden --format '{{.ID}}') | ||||||
|  | 
 | ||||||
|  | # stop all running instances | ||||||
|  | for INSTANCE in ${OLDINSTANCES[@]}; do | ||||||
|  | 	docker stop $INSTANCE | ||||||
|  | 	docker rm $INSTANCE | ||||||
|  | done | ||||||
|  | 
 | ||||||
|  | # update bitwarden itself | ||||||
|  | if [ "$1" = "y" ]; then | ||||||
|  | 	docker pull bitwarden/self-host:beta | ||||||
|  | else | ||||||
|  | 	read -p "Update (or get) bitwarden source container: " -n 1 -r | ||||||
|  | 	echo | ||||||
|  | 	if [[ $REPLY =~ ^[Yy]$ ]] | ||||||
|  | 	then | ||||||
|  | 		docker pull bitwarden/self-host:beta | ||||||
|  | 	fi | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # stop and remove previous existing patch(ed) container | ||||||
|  | docker stop bitwarden-patch | ||||||
|  | docker rm bitwarden-patch | ||||||
|  | docker image rm bitwarden-patch | ||||||
|  | 
 | ||||||
|  | # start a new bitwarden instance so we can patch it | ||||||
|  | PATCHINSTANCE=$(docker run -d --name bitwarden-patch bitwarden/self-host:beta) | ||||||
|  | 
 | ||||||
|  | # create our temporary directory | ||||||
|  | mkdir $TEMPDIRECTORY | ||||||
|  | 
 | ||||||
|  | # extract the files that need to be patched from the services that need to be patched into our temporary directory | ||||||
|  | for COMPONENT in ${COMPONENTS[@]}; do | ||||||
|  | 	mkdir "$TEMPDIRECTORY/$COMPONENT" | ||||||
|  | 	docker cp $PATCHINSTANCE:/app/$COMPONENT/Core.dll "$TEMPDIRECTORY/$COMPONENT/Core.dll" | ||||||
|  | done | ||||||
|  | 
 | ||||||
|  | # 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" | ||||||
|  | 
 | ||||||
|  | # start all user requested instances | ||||||
|  | cat "$PWD/.servers/serverlist.txt" | while read LINE; do | ||||||
|  | 	bash -c "$LINE" | ||||||
|  | done | ||||||
|  | 
 | ||||||
|  | # remove our bitBetter image | ||||||
|  | docker image rm bitbetter/bitbetter | ||||||
|  | 
 | ||||||
|  | # 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" | ||||||
							
								
								
									
										22
									
								
								generateKeys.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								generateKeys.ps1
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | # get the basic openssl binary path | ||||||
|  | $opensslbinary = "$Env:Programfiles\OpenSSL-Win64\bin\openssl.exe" | ||||||
|  | 
 | ||||||
|  | # if openssl is not installed attempt to install it | ||||||
|  | if (!(Get-Command $opensslbinary -errorAction SilentlyContinue)) | ||||||
|  | { | ||||||
|  |     winget install openssl | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # if previous keys exist, remove them | ||||||
|  | if (Test-Path "$pwd\.keys") | ||||||
|  | { | ||||||
|  | 	Remove-Item "$pwd\.keys" -Recurse -Force | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # create new directory  | ||||||
|  | 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' pkcs12 -export -out `"$pwd\.keys\cert.pfx`" -inkey `"$pwd\.keys\key.pem`" -in `"$pwd\.keys\cert.pem`" -passin pass:test -passout pass:test" | ||||||
|  | @ -1,20 +1,19 @@ | ||||||
| #!/bin/sh | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| # Check for openssl | # Check for openssl | ||||||
| command -v openssl >/dev/null 2>&1 || { echo >&2 "openssl required but not found.  Aborting."; exit 1; } | command -v openssl >/dev/null 2>&1 || { echo >&2 "openssl required but not found.  Aborting."; exit 1; } | ||||||
| 
 | 
 | ||||||
| DIR=`dirname "$0"` | DIR="$PWD/.keys" | ||||||
| DIR=`exec 2>/dev/null;(cd -- "$DIR") && cd -- "$DIR"|| cd "$DIR"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd` |  | ||||||
| 
 | 
 | ||||||
| # Remove any existing key files | # if previous keys exist, remove them | ||||||
| [ ! -e "$DIR/cert.pem" ]  || rm "$DIR/cert.pem" | if [ -d "$DIR" ]; then | ||||||
| [ ! -e "$DIR/key.pem" ]   || rm "$DIR/key.pem" | 	rm -rf "$DIR" | ||||||
| [ ! -e "$DIR/cert.cert" ] || rm "$DIR/cert.cert" | fi | ||||||
| [ ! -e "$DIR/cert.pfx" ]  || rm "$DIR/cert.pfx" | 
 | ||||||
|  | # create new directory  | ||||||
|  | mkdir "$DIR" | ||||||
| 
 | 
 | ||||||
| # Generate new keys | # 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	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 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 | openssl pkcs12 -export -out "$DIR/cert.pfx" -inkey "$DIR/key.pem" -in "$DIR/cert.pem" -passin pass:test -passout pass:test | ||||||
| 
 |  | ||||||
| ls |  | ||||||
							
								
								
									
										14
									
								
								licenseGen.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								licenseGen.ps1
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | if ($($args.Count) -lt 1) { | ||||||
|  |     echo "USAGE: <License Gen action> [License Gen args...]" | ||||||
|  |     echo "ACTIONS:" | ||||||
|  |     echo " interactive" | ||||||
|  |     echo " user" | ||||||
|  |     echo " org" | ||||||
|  | 	Exit 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if ($args[0] = "interactive") { | ||||||
|  |     docker run -it --rm bitbetter/licensegen interactive | ||||||
|  | } else { | ||||||
|  |     docker run bitbetter/licensegen $args | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								licenseGen.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										16
									
								
								licenseGen.sh
									
									
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | if [ $# -lt 1 ]; then | ||||||
|  |     echo "USAGE: <License Gen action> [License Gen args...]" | ||||||
|  |     echo "ACTIONS:" | ||||||
|  |     echo " interactive" | ||||||
|  |     echo " user" | ||||||
|  |     echo " org" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ "$1" = "interactive" ]; then | ||||||
|  | 	docker run -it --rm bitbetter/licensegen interactive | ||||||
|  | else | ||||||
|  | 	docker run --rm bitbetter/licensegen "$@" | ||||||
|  | fi | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| ARG BITWARDEN_TAG | FROM mcr.microsoft.com/dotnet/sdk:6.0 as build | ||||||
| FROM ${BITWARDEN_TAG} | WORKDIR /bitBetter | ||||||
| 
 | 
 | ||||||
| COPY bin/Debug/netcoreapp6.0/publish/* /bitBetter/ | COPY . /bitBetter | ||||||
| COPY ./.keys/cert.cert /newLicensing.cer | COPY cert.cert /app/ | ||||||
| 
 | 
 | ||||||
| RUN set -e; set -x; \ | RUN dotnet restore | ||||||
|     dotnet /bitBetter/bitBetter.dll && \ | RUN dotnet publish -c Release -o /app --no-restore | ||||||
|     mv /app/Core.dll /app/Core.orig.dll && \ | 
 | ||||||
|     mv /app/modified.dll /app/Core.dll && \ | FROM mcr.microsoft.com/dotnet/sdk:6.0 | ||||||
|     rm -rf /bitBetter && rm -rf /newLicensing.cer | WORKDIR /app | ||||||
|  | COPY --from=build /app . | ||||||
|  | 
 | ||||||
|  | ENTRYPOINT [ "/app/bitBetter" ] | ||||||
							
								
								
									
										4
									
								
								src/bitBetter/Dockerfile-bitwarden-patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/bitBetter/Dockerfile-bitwarden-patch
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | FROM bitwarden/self-host:beta | ||||||
|  | 
 | ||||||
|  | COPY ./temp/Api/Core.dll /app/Api/Core.dll | ||||||
|  | COPY ./temp/Identity/Core.dll /app/Identity/Core.dll | ||||||
|  | @ -1,93 +1,75 @@ | ||||||
| using System; | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Security.Cryptography.X509Certificates; | using System.Security.Cryptography.X509Certificates; | ||||||
| using Mono.Cecil; | using dnlib.DotNet; | ||||||
| using Mono.Cecil.Cil; | using dnlib.DotNet.Emit; | ||||||
| using Mono.Cecil.Rocks; | using dnlib.DotNet.Writer; | ||||||
|  | using dnlib.IO; | ||||||
| 
 | 
 | ||||||
| namespace bitwardenSelfLicensor | namespace bitBetter; | ||||||
|  | 
 | ||||||
|  | internal class Program | ||||||
| { | { | ||||||
|     class Program |     private static Int32 Main(String[] args) | ||||||
|     { |     { | ||||||
|         static int Main(string[] args) |         const String certFile = "/app/cert.cert"; | ||||||
|  |         String[] files = Directory.GetFiles("/app/mount", "Core.dll", SearchOption.AllDirectories); | ||||||
|  | 
 | ||||||
|  |         foreach (String file in files) | ||||||
|         { |         { | ||||||
|             string cerFile; |             Console.WriteLine(file); | ||||||
|             string corePath; |             ModuleDefMD moduleDefMd = ModuleDefMD.Load(file); | ||||||
|  |             Byte[] cert = File.ReadAllBytes(certFile); | ||||||
| 
 | 
 | ||||||
|             if(args.Length >= 2) { |             EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources | ||||||
|                 cerFile = args[0]; |                 .OfType<EmbeddedResource>() | ||||||
|                 corePath = args[1]; |                 .First(r => r.Name.Equals("Bit.Core.licensing.cer")); | ||||||
|             } else if (args.Length == 1) { |  | ||||||
|                 cerFile = args[0]; |  | ||||||
|                 corePath = "/app/Core.dll"; |  | ||||||
|             } |  | ||||||
|             else { |  | ||||||
|                 cerFile = "/newLicensing.cer"; |  | ||||||
|                 corePath = "/app/Core.dll"; |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|  |             Console.WriteLine(embeddedResourceToRemove.Name); | ||||||
| 
 | 
 | ||||||
|             var module =  ModuleDefinition.ReadModule(new MemoryStream(File.ReadAllBytes(corePath))); |             EmbeddedResource embeddedResourceToAdd = new("Bit.Core.licensing.cer", cert) {Attributes = embeddedResourceToRemove.Attributes }; | ||||||
|             var cert = File.ReadAllBytes(cerFile); |             moduleDefMd.Resources.Add(embeddedResourceToAdd); | ||||||
|  |             moduleDefMd.Resources.Remove(embeddedResourceToRemove); | ||||||
| 
 | 
 | ||||||
|             var x = module.Resources.OfType<EmbeddedResource>() |             DataReader reader = embeddedResourceToRemove.CreateReader(); | ||||||
|                                     .Where(r => r.Name.Equals("Bit.Core.licensing.cer")) |             X509Certificate2 existingCert = new(reader.ReadRemainingBytes()); | ||||||
|                                     .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}"); |             Console.WriteLine($"Existing Cert Thumbprint: {existingCert.Thumbprint}"); | ||||||
|             X509Certificate2 certificate = new X509Certificate2(cert); |             X509Certificate2 certificate = new(cert); | ||||||
| 
 | 
 | ||||||
|             Console.WriteLine($"New Cert Thumbprint: {certificate.Thumbprint}"); |             Console.WriteLine($"New Cert Thumbprint: {certificate.Thumbprint}"); | ||||||
| 
 | 
 | ||||||
|             var ctor = licensingType.GetConstructors().Single(); |             IEnumerable<TypeDef> services = moduleDefMd.Types.Where(t => t.Namespace == "Bit.Core.Services"); | ||||||
|  |             TypeDef type = services.First(t => t.Name == "LicensingService"); | ||||||
|  |             MethodDef constructor = type.FindConstructors().First(); | ||||||
|              |              | ||||||
|  |             Instruction instructionToPatch = | ||||||
|  |                 constructor.Body.Instructions | ||||||
|  |                     .FirstOrDefault(i => i.OpCode == OpCodes.Ldstr | ||||||
|  |                                          && String.Equals((String)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase)); | ||||||
|              |              | ||||||
|             var rewriter = ctor.Body.GetILProcessor(); |             if (instructionToPatch != null) | ||||||
| 
 |             { | ||||||
|             var instToReplace =  |                 instructionToPatch.Operand = certificate.Thumbprint; | ||||||
|                 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 { |             else | ||||||
|                 Console.WriteLine("Cant find inst"); |             { | ||||||
|  |                 Console.WriteLine("Can't find constructor to patch"); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // foreach (var inst in ctor.Body.Instructions) |             ModuleWriterOptions moduleWriterOptions = new(moduleDefMd); | ||||||
|             // { |             moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.KeepOldMaxStack; | ||||||
|             //     Console.Write(inst.OpCode.Name + " " + inst.Operand?.GetType() + " = "); |             moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveAll; | ||||||
|             //     if(inst.OpCode.FlowControl == FlowControl.Call) { |             moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveRids; | ||||||
|             //         Console.WriteLine(inst.Operand); |  | ||||||
|             //     } |  | ||||||
|             //     else if(inst.OpCode == OpCodes.Ldstr) { |  | ||||||
|             //         Console.WriteLine(inst.Operand); |  | ||||||
|             //     } |  | ||||||
|             //     else {Console.WriteLine();} |  | ||||||
|             // } |  | ||||||
| 
 | 
 | ||||||
|             module.Write("modified.dll"); |             moduleDefMd.Write(file + ".new"); | ||||||
|  |             moduleDefMd.Dispose(); | ||||||
|  |             File.Delete(file); | ||||||
|  |             File.Move(file + ".new", file); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | @ -6,7 +6,7 @@ | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="Mono.Cecil" Version="0.11.2" /> |     <PackageReference Include="dnlib" Version="3.6.0" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
| </Project> | </Project> | ||||||
|  |  | ||||||
|  | @ -1,7 +0,0 @@ | ||||||
| #!/bin/bash |  | ||||||
| 
 |  | ||||||
| set -e |  | ||||||
| set -x |  | ||||||
| 
 |  | ||||||
| dotnet restore |  | ||||||
| dotnet publish |  | ||||||
|  | @ -1,17 +1,15 @@ | ||||||
| FROM mcr.microsoft.com/dotnet/sdk:6.0 as build | FROM mcr.microsoft.com/dotnet/sdk:6.0 as build | ||||||
| 
 |  | ||||||
| WORKDIR /licenseGen | WORKDIR /licenseGen | ||||||
| 
 | 
 | ||||||
| COPY . /licenseGen | COPY . /licenseGen | ||||||
|  | COPY Core.dll /app/ | ||||||
|  | COPY cert.pfx /app/ | ||||||
| 
 | 
 | ||||||
| RUN set -e; set -x; \ | RUN dotnet restore | ||||||
| 	dotnet add package Newtonsoft.Json --version 13.0.1 \ | RUN dotnet publish -c Release -o /app --no-restore | ||||||
| 	&& dotnet restore \ |  | ||||||
| 	&& dotnet publish |  | ||||||
| 
 | 
 | ||||||
|  | FROM mcr.microsoft.com/dotnet/sdk:6.0 | ||||||
|  | WORKDIR /app | ||||||
|  | COPY --from=build /app . | ||||||
| 
 | 
 | ||||||
| FROM bitbetter/api | ENTRYPOINT [ "dotnet", "/app/licenseGen.dll", "--core", "/app/Core.dll", "--cert", "/app/cert.pfx" ] | ||||||
| 
 |  | ||||||
| COPY --from=build /licenseGen/bin/Debug/netcoreapp6.0/publish/* /app/ |  | ||||||
| 
 |  | ||||||
| ENTRYPOINT [ "dotnet", "/app/licenseGen.dll", "--core", "/app/Core.dll", "--cert", "/cert.pfx" ] |  | ||||||
|  |  | ||||||
|  | @ -1,159 +1,171 @@ | ||||||
| using System; | using System; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Linq; | using System.Reflection; | ||||||
| using System.Runtime.Loader; | using System.Runtime.Loader; | ||||||
| using System.Security.Cryptography.X509Certificates; | using System.Security.Cryptography.X509Certificates; | ||||||
| using Microsoft.Extensions.CommandLineUtils; | using Microsoft.Extensions.CommandLineUtils; | ||||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||||
| 
 | 
 | ||||||
| namespace bitwardenSelfLicensor | namespace licenseGen; | ||||||
| { |  | ||||||
|     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() | 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()); |             return File.Exists(cert.Value()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|             bool coreExists() |         Boolean CoreExists() | ||||||
|         { |         { | ||||||
|             return File.Exists(coreDll.Value()); |             return File.Exists(coreDll.Value()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|             bool verifyTopOptions() |         Boolean VerifyTopOptions() | ||||||
|         { |         { | ||||||
|                 return !string.IsNullOrWhiteSpace(cert.Value()) && |             return !String.IsNullOrWhiteSpace(cert.Value()) && | ||||||
|                        !string.IsNullOrWhiteSpace(coreDll.Value()) && |                    !String.IsNullOrWhiteSpace(coreDll.Value()) && | ||||||
|                        certExists() && coreExists(); |                    CertExists() && CoreExists(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         app.Command("interactive", config => |         app.Command("interactive", config => | ||||||
|         { |         { | ||||||
|                 string buff="", licensetype="", name="", email="", businessname=""; |             String buff, licensetype="", name="", email="", businessname=""; | ||||||
|                 short storage = 0; |             Int16 storage = 0; | ||||||
| 
 | 
 | ||||||
|                 bool valid_guid = false, valid_installid = false; |             Boolean validGuid = false, validInstallid = false; | ||||||
|                 Guid guid = new Guid(), installid = new Guid(); |             Guid guid = new(), installid = new(); | ||||||
| 
 | 
 | ||||||
|             config.OnExecute(() => |             config.OnExecute(() => | ||||||
|             { |             { | ||||||
|                     if (!verifyTopOptions()) |                 if (!VerifyTopOptions()) | ||||||
|                 { |                 { | ||||||
|                         if (!coreExists()) config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); |                     if (!CoreExists()) config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); | ||||||
|                         if (!certExists()) config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); |                     if (!CertExists()) config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); | ||||||
| 
 | 
 | ||||||
|                     config.ShowHelp(); |                     config.ShowHelp(); | ||||||
|                     return 1; |                     return 1; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                     WriteLine("Interactive license mode..."); |                 Console.WriteLine("Interactive license mode..."); | ||||||
|                  |                  | ||||||
|                 while (licensetype == "") |                 while (licensetype == "") | ||||||
|                 { |                 { | ||||||
|                         WriteLine("What would you like to generate, a [u]ser license or an [o]rg license?"); |                     Console.WriteLine("What would you like to generate, a [u]ser license or an [o]rg license: "); | ||||||
|                     buff = Console.ReadLine(); |                     buff = Console.ReadLine(); | ||||||
| 
 | 
 | ||||||
|                     if(buff == "u") |                     if(buff == "u") | ||||||
|                     { |                     { | ||||||
|                         licensetype = "user"; |                         licensetype = "user"; | ||||||
|                             WriteLineOver("Okay, we will generate a user license."); |                         Console.WriteLine("Okay, we will generate a user license."); | ||||||
| 
 | 
 | ||||||
|                             while (valid_guid == false) |                         while (validGuid == false) | ||||||
|                         { |                         { | ||||||
|                                 WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]:"); |                             Console.WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]: "); | ||||||
|                             buff = Console.ReadLine(); |                             buff = Console.ReadLine(); | ||||||
| 
 | 
 | ||||||
|                                 if (Guid.TryParse(buff, out guid))valid_guid = true; |                             if (Guid.TryParse(buff, out guid))validGuid = true; | ||||||
|                                 else WriteLineOver("The user-guid provided does not appear to be valid."); |                             else Console.WriteLine("The user-guid provided does not appear to be valid!"); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     else if (buff == "o") |                     else if (buff == "o") | ||||||
|                     { |                     { | ||||||
|                         licensetype = "org"; |                         licensetype = "org"; | ||||||
|                             WriteLineOver("Okay, we will generate an organization license."); |                         Console.WriteLine("Okay, we will generate an organization license."); | ||||||
| 
 | 
 | ||||||
|                             while (valid_installid == false) |                         while (validInstallid == false) | ||||||
|                         { |                         { | ||||||
|                                 WriteLine("Please provide your Bitwarden Install-ID — refer to the Readme for details on how to retrieve this. [Install-ID]:"); |                             Console.WriteLine("Please provide your Bitwarden Install-ID — refer to the Readme for details on how to retrieve this. [Install-ID]: "); | ||||||
|                             buff = Console.ReadLine(); |                             buff = Console.ReadLine(); | ||||||
| 
 | 
 | ||||||
|                                 if (Guid.TryParse(buff, out installid)) valid_installid = true; |                             if (Guid.TryParse(buff, out installid)) validInstallid = true; | ||||||
|                                 else WriteLineOver("The install-id provided does not appear to be valid."); |                             else Console.WriteLine("The install-id provided does not appear to be valid."); | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         while (businessname == "") |                         while (businessname == "") | ||||||
|                         { |                         { | ||||||
|                                 WriteLineOver("Please enter a business name, default is BitBetter. [Business Name]:"); |                             Console.WriteLine("Please enter a business name, default is BitBetter. [Business Name]: "); | ||||||
|                             buff = Console.ReadLine(); |                             buff = Console.ReadLine(); | ||||||
|                                 if (buff == "")                     businessname = "BitBetter"; |                             if (buff == "") | ||||||
|                                 else if (checkBusinessName(buff))   businessname = buff; |                             { | ||||||
|  |                                 businessname = "BitBetter"; | ||||||
|  |                             } | ||||||
|  |                             else if (CheckBusinessName(buff)) | ||||||
|  |                             { | ||||||
|  |                                 businessname = buff; | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     else |                     else | ||||||
|                     { |                     { | ||||||
|                             WriteLineOver("Unrecognized option \'" + buff + "\'. "); |                         Console.WriteLine("Unrecognized option \'" + buff + "\'."); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 while (name == "") |                 while (name == "") | ||||||
|                 { |                 { | ||||||
|                         WriteLineOver("Please provide the username this license will be registered to. [username]:"); |                     Console.WriteLine("Please provide the username this license will be registered to. [username]: "); | ||||||
|                     buff = Console.ReadLine(); |                     buff = Console.ReadLine(); | ||||||
|                         if ( checkUsername(buff) )   name = buff; |                     if ( CheckUsername(buff) )   name = buff; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 while (email == "") |                 while (email == "") | ||||||
|                 { |                 { | ||||||
|                         WriteLineOver("Please provide the email address for the user " + name + ". [email]"); |                     Console.WriteLine("Please provide the email address for the user " + name + ". [email]: "); | ||||||
|                     buff = Console.ReadLine(); |                     buff = Console.ReadLine(); | ||||||
|                         if ( checkEmail(buff) )   email = buff; |                     if (CheckEmail(buff)) | ||||||
|  |                     { | ||||||
|  |                         email = buff; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 while (storage == 0) |                 while (storage == 0) | ||||||
|                 { |                 { | ||||||
|                         WriteLineOver("Extra storage space for the user " + name + ". (max.: " + short.MaxValue + "). Defaults to maximum value. [storage]"); |                     Console.WriteLine("Extra storage space for the user " + name + ". (max.: " + Int16.MaxValue + "). Defaults to maximum value. [storage]"); | ||||||
|                     buff = Console.ReadLine(); |                     buff = Console.ReadLine(); | ||||||
|                         if (string.IsNullOrWhiteSpace(buff)) |                     if (String.IsNullOrWhiteSpace(buff)) | ||||||
|                     { |                     { | ||||||
|                             storage = short.MaxValue; |                         storage = Int16.MaxValue; | ||||||
|                     } |                     } | ||||||
|                     else |                     else | ||||||
|                     { |                     { | ||||||
|                             if (checkStorage(buff)) storage = short.Parse(buff); |                         if (CheckStorage(buff)) | ||||||
|  |                         { | ||||||
|  |                             storage = Int16.Parse(buff); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if (licensetype == "user") |                 if (licensetype == "user") | ||||||
|                 { |                 { | ||||||
|                         WriteLineOver("Confirm creation of \"user\" license for username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", User-GUID: \"" + guid + "\"? Y/n"); |                     Console.WriteLine("Confirm creation of \"user\" license for username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", User-GUID: \"" + guid + "\"? Y/n"); | ||||||
|                     buff = Console.ReadLine(); |                     buff = Console.ReadLine(); | ||||||
|                         if ( buff == "" || buff == "y" || buff == "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 |                     else | ||||||
|                     { |                     { | ||||||
|                             WriteLineOver("Exiting..."); |                         Console.WriteLine("Exiting..."); | ||||||
|                         return 0; |                         return 0; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 else if (licensetype == "org") |                 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"); |                     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(); |                     buff = Console.ReadLine(); | ||||||
|                         if ( buff == "" || buff == "y" || buff == "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 |                     else | ||||||
|                     { |                     { | ||||||
|                             WriteLineOver("Exiting..."); |                         Console.WriteLine("Exiting..."); | ||||||
|                         return 0; |                         return 0; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -164,22 +176,21 @@ namespace bitwardenSelfLicensor | ||||||
| 
 | 
 | ||||||
|         app.Command("user", config => |         app.Command("user", config => | ||||||
|         { |         { | ||||||
|                 var name = config.Argument("Name", "your name"); |             CommandArgument name = config.Argument("Name", "your name"); | ||||||
|                 var email = config.Argument("Email", "your email"); |             CommandArgument email = config.Argument("Email", "your email"); | ||||||
|                 var userIdArg = config.Argument("User ID", "your user id"); |             CommandArgument 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)"); |             CommandArgument storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + Int16.MaxValue + " (optional, default = max)"); | ||||||
|                 var key = config.Argument("Key", "your key id (optional)"); |             CommandArgument key = config.Argument("Key", "your key id (optional)"); | ||||||
|                 var help = config.HelpOption("--help | -h | -?"); |  | ||||||
| 
 | 
 | ||||||
|             config.OnExecute(() => |             config.OnExecute(() => | ||||||
|             { |             { | ||||||
|                     if (!verifyTopOptions()) |                 if (!VerifyTopOptions()) | ||||||
|                 { |                 { | ||||||
|                         if (!coreExists()) |                     if (!CoreExists()) | ||||||
|                     { |                     { | ||||||
|                         config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); |                         config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); | ||||||
|                     } |                     } | ||||||
|                         if (!certExists()) |                     if (!CertExists()) | ||||||
|                     { |                     { | ||||||
|                         config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); |                         config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); | ||||||
|                     } |                     } | ||||||
|  | @ -187,31 +198,32 @@ namespace bitwardenSelfLicensor | ||||||
|                     config.ShowHelp(); |                     config.ShowHelp(); | ||||||
|                     return 1; |                     return 1; | ||||||
|                 } |                 } | ||||||
|                     else if (string.IsNullOrWhiteSpace(name.Value) || string.IsNullOrWhiteSpace(email.Value)) | 
 | ||||||
|  |                 if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value)) | ||||||
|                 { |                 { | ||||||
|                     config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}'"); |                     config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}'"); | ||||||
|                     config.ShowHelp("user"); |                     config.ShowHelp("user"); | ||||||
|                     return 1; |                     return 1; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                     if (string.IsNullOrWhiteSpace(userIdArg.Value) || !Guid.TryParse(userIdArg.Value, out Guid userId)) |                 if (String.IsNullOrWhiteSpace(userIdArg.Value) || !Guid.TryParse(userIdArg.Value, out Guid userId)) | ||||||
|                 { |                 { | ||||||
|                         config.Error.WriteLine($"User ID not provided"); |                     config.Error.WriteLine("User ID not provided"); | ||||||
|                     config.ShowHelp("user"); |                     config.ShowHelp("user"); | ||||||
|                     return 1; |                     return 1; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                     short storageShort = 0; |                 Int16 storageShort = 0; | ||||||
|                     if (!string.IsNullOrWhiteSpace(storage.Value)) |                 if (!String.IsNullOrWhiteSpace(storage.Value)) | ||||||
|                 { |                 { | ||||||
|                         var parsedStorage = double.Parse(storage.Value); |                     Double parsedStorage = Double.Parse(storage.Value); | ||||||
|                         if (parsedStorage > short.MaxValue || parsedStorage < 0) |                     if (parsedStorage is > Int16.MaxValue or < 0) | ||||||
|                     { |                     { | ||||||
|                             config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + short.MaxValue + "]"); |                         config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]"); | ||||||
|                         config.ShowHelp("org"); |                         config.ShowHelp("org"); | ||||||
|                         return 1; |                         return 1; | ||||||
|                     } |                     } | ||||||
|                         storageShort = (short) parsedStorage; |                     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); | ||||||
|  | @ -221,23 +233,22 @@ namespace bitwardenSelfLicensor | ||||||
|         }); |         }); | ||||||
|         app.Command("org", config => |         app.Command("org", config => | ||||||
|         { |         { | ||||||
|                 var name = config.Argument("Name", "your name"); |             CommandArgument name = config.Argument("Name", "your name"); | ||||||
|                 var email = config.Argument("Email", "your email"); |             CommandArgument email = config.Argument("Email", "your email"); | ||||||
|                 var installId = config.Argument("InstallId", "your installation id (GUID)"); |             CommandArgument 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)"); |             CommandArgument storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + Int16.MaxValue + " (optional, default = max)"); | ||||||
|                 var businessName = config.Argument("BusinessName", "name for the organization (optional)"); |             CommandArgument businessName = config.Argument("BusinessName", "name for the organization (optional)"); | ||||||
|                 var key = config.Argument("Key", "your key id (optional)"); |             CommandArgument key = config.Argument("Key", "your key id (optional)"); | ||||||
|                 var help = config.HelpOption("--help | -h | -?"); |  | ||||||
|              |              | ||||||
|             config.OnExecute(() => |             config.OnExecute(() => | ||||||
|             { |             { | ||||||
|                     if (!verifyTopOptions()) |                 if (!VerifyTopOptions()) | ||||||
|                 { |                 { | ||||||
|                         if (!coreExists()) |                     if (!CoreExists()) | ||||||
|                     { |                     { | ||||||
|                         config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); |                         config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); | ||||||
|                     } |                     } | ||||||
|                         if (!certExists()) |                     if (!CertExists()) | ||||||
|                     { |                     { | ||||||
|                         config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); |                         config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); | ||||||
|                     } |                     } | ||||||
|  | @ -245,9 +256,10 @@ namespace bitwardenSelfLicensor | ||||||
|                     config.ShowHelp(); |                     config.ShowHelp(); | ||||||
|                     return 1; |                     return 1; | ||||||
|                 } |                 } | ||||||
|                     else if (string.IsNullOrWhiteSpace(name.Value) || | 
 | ||||||
|                             string.IsNullOrWhiteSpace(email.Value) || |                 if (String.IsNullOrWhiteSpace(name.Value) || | ||||||
|                             string.IsNullOrWhiteSpace(installId.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.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}' InstallId='{installId.Value}'"); | ||||||
|                     config.ShowHelp("org"); |                     config.ShowHelp("org"); | ||||||
|  | @ -262,17 +274,17 @@ namespace bitwardenSelfLicensor | ||||||
|                     return 1; |                     return 1; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                     short storageShort = 0; |                 Int16 storageShort = 0; | ||||||
|                     if (!string.IsNullOrWhiteSpace(storage.Value)) |                 if (!String.IsNullOrWhiteSpace(storage.Value)) | ||||||
|                 { |                 { | ||||||
|                         var parsedStorage = double.Parse(storage.Value); |                     Double parsedStorage = Double.Parse(storage.Value); | ||||||
|                         if (parsedStorage > short.MaxValue || parsedStorage < 0) |                     if (parsedStorage is > Int16.MaxValue or < 0) | ||||||
|                     { |                     { | ||||||
|                             config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + short.MaxValue + "]"); |                         config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]"); | ||||||
|                         config.ShowHelp("org"); |                         config.ShowHelp("org"); | ||||||
|                         return 1; |                         return 1; | ||||||
|                     } |                     } | ||||||
|                         storageShort = (short) 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); | ||||||
|  | @ -301,123 +313,109 @@ namespace bitwardenSelfLicensor | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // checkUsername Checks that the username is a valid username |     // checkUsername Checks that the username is a valid username | ||||||
|         static bool checkUsername(string s) |     private static Boolean CheckUsername(String s) | ||||||
|     { |     { | ||||||
|             if ( string.IsNullOrWhiteSpace(s) ) { |         // TODO: Actually validate | ||||||
|                 WriteLineOver("The username provided doesn't appear to be valid.\n"); |         if (!String.IsNullOrWhiteSpace(s)) return true; | ||||||
|  | 
 | ||||||
|  |         Console.WriteLine("The username provided doesn't appear to be valid!"); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|             return true;    // TODO: Actually validate |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|     // checkBusinessName Checks that the Business Name is a valid username |     // checkBusinessName Checks that the Business Name is a valid username | ||||||
|         static bool checkBusinessName(string s) |     private static Boolean CheckBusinessName(String s) | ||||||
|     { |     { | ||||||
|             if ( string.IsNullOrWhiteSpace(s) ) { |         // TODO: Actually validate | ||||||
|                 WriteLineOver("The Business Name provided doesn't appear to be valid.\n"); |         if (!String.IsNullOrWhiteSpace(s)) return true; | ||||||
|  | 
 | ||||||
|  |         Console.WriteLine("The Business Name provided doesn't appear to be valid!"); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|             return true;    // TODO: Actually validate |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|     // checkEmail Checks that the email address is a valid email address |     // checkEmail Checks that the email address is a valid email address | ||||||
|         static bool checkEmail(string s) |     private static Boolean CheckEmail(String s) | ||||||
|     { |     { | ||||||
|             if ( string.IsNullOrWhiteSpace(s) ) { |         // TODO: Actually validate | ||||||
|                 WriteLineOver("The email provided doesn't appear to be valid.\n"); |         if (!String.IsNullOrWhiteSpace(s)) return true; | ||||||
|  | 
 | ||||||
|  |         Console.WriteLine("The email provided doesn't appear to be valid!"); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|             return true;    // TODO: Actually validate |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|     // checkStorage Checks that the storage is in a valid range |     // checkStorage Checks that the storage is in a valid range | ||||||
|         static bool checkStorage(string s) |     private static Boolean CheckStorage(String s) | ||||||
|     { |     { | ||||||
|             if (string.IsNullOrWhiteSpace(s)) |         if (String.IsNullOrWhiteSpace(s)) | ||||||
|         { |         { | ||||||
|                 WriteLineOver("The storage provided doesn't appear to be valid.\n"); |             Console.WriteLine("The storage provided doesn't appear to be valid!"); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|             if (double.Parse(s) > short.MaxValue || double.Parse(s) < 0) | 
 | ||||||
|             { |         if (!(Double.Parse(s) > Int16.MaxValue) && !(Double.Parse(s) < 0)) return true; | ||||||
|                 WriteLineOver("The storage value provided is outside the accepted range of [0-" + short.MaxValue + "].\n"); | 
 | ||||||
|  |         Console.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]!"); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         // WriteLineOver Writes a new line to console over last line. |     private static void GenerateUserLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid userId, String key) | ||||||
|         static void WriteLineOver(string s) |  | ||||||
|     { |     { | ||||||
|             Console.SetCursorPosition(0, Console.CursorTop -1); |         Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); | ||||||
|             Console.WriteLine(s); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         // WriteLine This wrapper is just here so that console writes all look similar. |         Type type = core.GetType("Bit.Core.Models.Business.UserLicense"); | ||||||
|         static void WriteLine(string s) |         Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); | ||||||
|         { |  | ||||||
|             Console.WriteLine(s); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         static void GenerateUserLicense(X509Certificate2 cert, string corePath, string userName, string email, short storage, Guid userId, string key) |         Object license = Activator.CreateInstance(type); | ||||||
|         { |  | ||||||
|             var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); |  | ||||||
| 
 | 
 | ||||||
|             var type = core.GetType("Bit.Core.Models.Business.UserLicense"); |         void Set(String name, Object value) | ||||||
|             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); |             type.GetProperty(name).SetValue(license, value); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|             set("LicenseKey", string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); |         Set("LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); | ||||||
|             set("Id", userId); |         Set("Id", userId); | ||||||
|             set("Name", userName); |         Set("Name", userName); | ||||||
|             set("Email", email); |         Set("Email", email); | ||||||
|             set("Premium", true); |         Set("Premium", true); | ||||||
|             set("MaxStorageGb", storage == 0 ? short.MaxValue : storage); |         Set("MaxStorageGb", storage == 0 ? Int16.MaxValue : storage); | ||||||
|             set("Version", 1); |         Set("Version", 1); | ||||||
|             set("Issued", DateTime.UtcNow); |         Set("Issued", DateTime.UtcNow); | ||||||
|             set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1)); |         Set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1)); | ||||||
|             set("Expires", DateTime.UtcNow.AddYears(100)); |         Set("Expires", DateTime.UtcNow.AddYears(100)); | ||||||
|             set("Trial", false); |         Set("Trial", false); | ||||||
|             set("LicenseType", Enum.Parse(licenseTypeEnum, "User")); |         Set("LicenseType", Enum.Parse(licenseTypeEnum, "User")); | ||||||
| 
 | 
 | ||||||
|             set("Hash", Convert.ToBase64String((byte[])type.GetMethod("ComputeHash").Invoke(license, new object[0]))); |         Set("Hash", Convert.ToBase64String((Byte[])type.GetMethod("ComputeHash").Invoke(license, new Object[0]))); | ||||||
|             set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert }))); |         Set("Signature", Convert.ToBase64String((Byte[])type.GetMethod("Sign").Invoke(license, new Object[] { cert }))); | ||||||
| 
 | 
 | ||||||
|         Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); |         Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|         static void GenerateOrgLicense(X509Certificate2 cert, string corePath, string userName, string email, short storage, Guid instalId, string businessName, string key) |     private static void GenerateOrgLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid instalId, String businessName, String key) | ||||||
|     { |     { | ||||||
|             var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); |         Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); | ||||||
| 
 | 
 | ||||||
|             var type = core.GetType("Bit.Core.Models.Business.OrganizationLicense"); |         Type type = core.GetType("Bit.Core.Models.Business.OrganizationLicense"); | ||||||
|             var licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); |         Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); | ||||||
|             var planTypeEnum = core.GetType("Bit.Core.Enums.PlanType"); |         Type planTypeEnum = core.GetType("Bit.Core.Enums.PlanType"); | ||||||
| 
 | 
 | ||||||
|             var license = Activator.CreateInstance(type); |         Object license = Activator.CreateInstance(type); | ||||||
| 
 | 
 | ||||||
|             void set(string name, object value) |         void set(String name, Object value) | ||||||
|         { |         { | ||||||
|             type.GetProperty(name).SetValue(license, value); |             type.GetProperty(name).SetValue(license, value); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|             set("LicenseKey", string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); |         set("LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); | ||||||
|         set("InstallationId", instalId); |         set("InstallationId", instalId); | ||||||
|         set("Id", Guid.NewGuid()); |         set("Id", Guid.NewGuid()); | ||||||
|         set("Name", userName); |         set("Name", userName); | ||||||
|         set("BillingEmail", email); |         set("BillingEmail", email); | ||||||
|             set("BusinessName", string.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName); |         set("BusinessName", String.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName); | ||||||
|         set("Enabled", true); |         set("Enabled", true); | ||||||
|         set("Plan", "Custom"); |         set("Plan", "Custom"); | ||||||
|         set("PlanType", Enum.Parse(planTypeEnum, "Custom")); |         set("PlanType", Enum.Parse(planTypeEnum, "Custom")); | ||||||
|             set("Seats", (int)short.MaxValue); |         set("Seats", (Int32)Int16.MaxValue); | ||||||
|             set("MaxCollections", short.MaxValue); |         set("MaxCollections", Int16.MaxValue); | ||||||
|         set("UsePolicies", true); |         set("UsePolicies", true); | ||||||
|         set("UseSso", true); |         set("UseSso", true); | ||||||
|         set("UseKeyConnector", true); |         set("UseKeyConnector", true); | ||||||
|  | @ -429,7 +427,7 @@ namespace bitwardenSelfLicensor | ||||||
|         set("Use2fa", true); |         set("Use2fa", true); | ||||||
|         set("UseApi", true); |         set("UseApi", true); | ||||||
|         set("UseResetPassword", true); |         set("UseResetPassword", true); | ||||||
|             set("MaxStorageGb", storage == 0 ? short.MaxValue : storage); |         set("MaxStorageGb", storage == 0 ? Int16.MaxValue : storage); | ||||||
|         set("SelfHost", true); |         set("SelfHost", true); | ||||||
|         set("UsersGetPremium", true); |         set("UsersGetPremium", true); | ||||||
|         set("Version", 9); |         set("Version", 9); | ||||||
|  | @ -439,10 +437,9 @@ namespace bitwardenSelfLicensor | ||||||
|         set("Trial", false); |         set("Trial", false); | ||||||
|         set("LicenseType", Enum.Parse(licenseTypeEnum, "Organization")); |         set("LicenseType", Enum.Parse(licenseTypeEnum, "Organization")); | ||||||
| 
 | 
 | ||||||
|             set("Hash", Convert.ToBase64String((byte[])type.GetMethod("ComputeHash").Invoke(license, new object[0]))); |         set("Hash", Convert.ToBase64String((Byte[])type.GetMethod("ComputeHash").Invoke(license, new Object[0]))); | ||||||
|             set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert }))); |         set("Signature", Convert.ToBase64String((Byte[])type.GetMethod("Sign").Invoke(license, new Object[] { cert }))); | ||||||
| 
 | 
 | ||||||
|         Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); |         Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); | ||||||
|     } |     } | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" /> |     <PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" /> | ||||||
|     <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> |     <PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> | ||||||
|     <PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> |     <PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 <ABSOLUTE PATH TO CERT.PFX> <License Gen action> [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 |  | ||||||
|  | @ -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!" |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user