Compare commits

..

52 Commits

Author SHA1 Message Date
Michiel Hazelhof
389be8cea8 Unified work (#269)
* Some work on line endings

* Enable buildkit

* Update documentation

* Settle the newline and tab vs spaces for now

Not perfect, but it's a standard

* Change wording

* Update version memo

* Add correct definition for markdown

* Make things clearer
2025-12-09 21:13:35 +01:00
Jackson K
f6d7470ce8 Update container name/links of Bitwarden Unified to Bitwarden Lite (#265)
* Rename Containers from bitwarden/self-host:beta to bitwarden/lite:beta

* Edit README to reflect rename

* Change build.ps1 and Dockerfile-bitwarden-patched to CRLF

* Remove .DS_Store file, and add to .gitignore

* Actually remove .DS_Store

* Make all ps1 files CRLF in .gitattributes & Make Dockerfiles LF.

* Change from beta tag to latest
2025-12-08 23:08:28 +01:00
Michiel Hazelhof
9bc010cb57 Update and fix unified branch (#257)
* Upstream patches

* Fix license generator according to upstream changes (#245) (#249)

* Test generating user and organization licenses during build check (#252)

* Fix ps1 script and update language

* Update class path

* Cleanup code

* Cleanup code

* Cleanup code

* Refactor and fixes

* Copy all files

* Copy files only when needed

* Make call consistent

* Simplify call

* Clarify language

* Reuse code

* Cleanup

* Cleanup

* Remove NewtonSoft.Json

* Upgrade dnlib

* Cleanup

* Fix path issue

* Fix comparator

* Cleanup circleci

* Fix type

* Fix circleci

* Properly detect previous version

* Add missing parameter

* Better detect running patched containers

* Improve naming

* Fix line endings

* Fix typo

* Add comment

* Fix tabs

* Cleanup org license

* Use proper file extension

* Add missing file

* Migrate cert.cert if exists

* Check for the correct file

* Fix character check

* Add comment

* Add more documentation

* Add proper line endings

* Add potentially correct line

* Add auto restart

* Update comment

* Improve consistency between bash and powerhell

* Update documentation

* Detect buildx

* Fix spelling mistake

* Fix check order and improve verbosity
2025-10-05 19:40:16 +01:00
juliokele
29add24126 licenseGen: fix OrganizationLicense namespace (#253)
Co-authored-by: juliokele <>
2025-08-03 18:11:30 +02:00
Joseph Gigantino
3689cc5ba1 Test generating user and organization licenses during build check (#251)
Add commands to build check to test if the created licensegen image can actually generate user and organization licenses. licenseGen.sh will print the generated license to stdout and return zero if successful. If an error occurs, a non zero error code is returned which should cause a build error.
2025-07-30 19:51:51 +02:00
Joseph Gigantino
34da077778 Update Program.cs (#241)
Remove duplicate instances of:
Set("UseRiskInsights", true);
Set("UseOrganizationDomains", true);     Set("UseAdminSponsoredFamilies", true);

Signed-off-by: Joseph Gigantino <128943406+Jgigantino31@users.noreply.github.com>
Co-authored-by: h44z <christoph.h@sprinternet.at>
2025-07-29 19:54:54 +02:00
juliokele
a3803cb3bc [unified] Fix licenseGen according to upstream changes (#247)
* Dockerfile: remove not existing executable argument

* licenseGen: fix classes according to upstream changes

---------

Co-authored-by: juliokele <>
2025-07-29 19:49:52 +02:00
juliokele
01cdfa2842 [unified] Fix patch according to upstream changes and fix build errors (#243)
* Fix bitbetter patch according to upstream changes

* Fix the builds by removing redundant already removed and stopped old instance

---------

Co-authored-by: juliokele <>
2025-07-29 19:48:55 +02:00
Michiel Hazelhof
076b0a624b Upstream patches (#240) 2025-07-15 10:00:09 +01:00
Joseph Gigantino
3d4c10d6f6 Update Program.cs (#236)
Set three new license options to true: UseRiskInsights, UseOrganizationDomains, and UseAdminSponsoredFamilies.

As mentioned in #229, licenses had to be regenerated starting in 2025.5.0. It looks like new options were added which default to false unless we set them to true here. I am not sure if the version needs to be bumped or not. I tested locally and I can now access the Claimed Domains page in my organization.

Signed-off-by: Joseph Gigantino <128943406+Jgigantino31@users.noreply.github.com>
2025-06-25 12:40:00 +00:00
Joseph Gigantino
1597800b89 Update serverlist.txt so that by default all lines are comments (#228) 2025-06-25 12:34:10 +00:00
juliokele
02740e84b6 build.sh: fix line endings in serverlist.txt before executing commands (#232)
Co-authored-by: Gyula Kelemen <kgydevelopement@gmail.com>
2025-06-10 20:47:53 +02:00
Joseph Gigantino
d71aa84e52 Update Unified branch to account for move from Docker Hub to GHCR (#227)
* Update build.sh to reflect move to GHCR

* Update build.ps1 to reflect move to GHCR

* Update README.md to reflect move to GHCR

* Update Dockerfile-bitwarden-patch to reflect move to GHCR
2025-03-29 17:15:16 +00:00
Michiel Hazelhof
770dcd33f6 Move Manually generating Certificate & Key section in README.md for unified (#220)
* Update Dockerfile

See #215 and #207

* Move Manually generating Certificate & Key section in README.md

See #216
2025-01-12 12:14:30 +01:00
Michiel Hazelhof
b6d2c9244c Update Dockerfile (#217)
See #215 and #207
2024-11-24 19:05:26 +01:00
Michiel Hazelhof
b47fe37279 Update license to version 15 (#211) (#212)
Co-authored-by: captainhook <16797541+captainhook@users.noreply.github.com>
2024-10-15 11:12:55 +01:00
Michiel Hazelhof
f75731633c Use new location (#201) 2024-06-30 01:30:58 +00:00
Michiel Hazelhof
0a45513872 Follow master updates and general improvements (#194)
* Use correct framework

* Upgrade packages

* Remove unused variable

* Migrate away from deprecated package

* Improve naming

* Fix typo

* Simplify constructors
2024-03-31 11:13:05 +01:00
Michiel Hazelhof
c8192610dc Properly fix Dockerfiles (#189) 2024-03-04 21:16:30 +00:00
Michiel Hazelhof
e4da85d46e Updated the unified branch (#180)
* Updated license version to 12, added SM options, increased max seats (short to int) (#172)

* - Updated license version to 12
- Added new SM license options
* Change seats, smseats, smserviceaccounts from short to int, like they are in the Bitwarden server code, to allow for the accurate maximum amount of seats

* Add extra ignore

* Code cleanup

* Ignore more VS cruft

* Bring up to date with upstream

(Update License to use Secrets Manager fully)

* Update to .NET 8.0
2024-02-23 14:44:27 +00:00
Michiel Hazelhof
38e6ebc5f9 Update licenseGen with latest version and options (#165) (#166)
Co-authored-by: Michiel Hazelhof <m.hazelhof@fyn.nl>
2023-04-23 12:41:27 +00:00
Michiel Hazelhof
d4abc9e5b7 Full Unified support including Linux and Windows (#155 / #154)
* 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
2023-01-16 21:13:43 +01:00
h44z
b819fe0c7d update to dotnet 6.0 (#148) (#150) 2022-09-01 14:43:34 -07:00
h44z
960894cff4 update/remove json lib (#146)
* update/remove json lib

* remove json lib
2022-08-16 13:34:17 -07:00
h44z
6038a8668b License gen fixes (#145)
* set default storage space to max (#122)

* update license fields to newest version

* strange characters for non-interactive mode (#123)
2022-08-16 22:32:39 +02:00
Alexander Detsch
101f078a98 Update README, E-Mail Verification constraint (#140)
thanks =)
2022-06-27 22:01:37 +02:00
Grommish
338ea68b08 BitBetter: update build.sh / update-bitwarden.sh (#135)
* BitBetter: update build.sh / update-bitwarden.sh

Bitwarden has changed the way they report version numbers for
self-hosted installations.

Fixes https://github.com/jakeswenson/BitBetter/issues/134

Credit to @Ayitaka for the fix

Tested and Verified, Updated install to 1.47.1

Signed-off-by: Donald Hoskins <grommish@gmail.com>

* Update build.sh

Remove extraneous comment
2022-04-08 21:49:08 -07:00
h44z
a58baf0186 Update README (#132)
- Update tested version
 - Add hint for upgrading from versions prior to 1.46.2
 - Add hint for Families Organization license
2022-03-22 11:49:13 +00:00
h44z
50cdb10b2c Fix #128 (#129)
Fix #128
2022-03-21 21:20:32 +01:00
Reliacon Developer
d2377d0f84 Update bitwarden.sh path (#127)
* Update bitwarden.sh path

* Update update-bitwarden.sh

Co-authored-by: Alex Pearce <alexpearce92@gmail.com>
2022-03-03 19:28:02 +01:00
Grommish
0f2f9274cf update-bitwarden.sh: Fix relative path (#111)
* update-bitwarden.sh: Fix relative path

update-bitwarden.sh attempted to update BitBetter via
./build.sh, but if run via crontab, you aren't in
the BitBetter directory.

Fixed to find it correctly.

Signed-off-by: Donald Hoskins <grommish@gmail.com>

* Update update-bitwarden.sh

Co-authored-by: h44z <christoph.h@sprinternet.at>

Co-authored-by: h44z <christoph.h@sprinternet.at>
2021-08-31 11:40:37 +02:00
clanto007
af2d744061 fix '3.1.0' was not found (#116)
* Update build.sh

* Update Dockerfile

* Update bitBetter.csproj

* Update Dockerfile

* Update Dockerfile

* Update licenseGen.csproj

* Apply suggestions from code review

Co-authored-by: h44z <christoph.h@sprinternet.at>

* Update src/licenseGen/Dockerfile

Co-authored-by: h44z <christoph.h@sprinternet.at>

* Update src/licenseGen/Dockerfile

Co-authored-by: h44z <christoph.h@sprinternet.at>
2021-08-31 10:18:29 +02:00
captainhook
a17681e82b fix integer type (#110)
fix integer type for GenerateOrgLicense's "Seats" value
2021-08-09 18:10:49 +02:00
captainhook
21bffcf262 Update update-bitwarden.sh (#95)
Prune Docker images to reclaim space
2021-03-17 10:38:54 +01:00
p0thi
59ee2e3c3a Make update-bitwarden.sh able to run non-interactive (#90)
* make update-bitwarden.sh able to run non-interactive

* changed script to be runnable by bash 3.x & README entry edited
2021-01-27 14:51:13 +01:00
kerenon
7ddf01fdf6 MaxStorageGb customization (#88)
* MaxStorageGb customization

* Defaulting to short.MaxValue if user entry is blank

* Update text prompt
2020-11-26 13:04:18 +00:00
captainhook
46934b0ddd Update README.md (#84)
Correct licenseGen build step

Co-authored-by: h44z <christoph.h@sprinternet.at>
2020-10-06 14:52:55 +01:00
Joe S
a5516220fd Fix wrong directory mistake, fix grammer (#83)
Fixed the license generator section
2020-10-06 15:49:47 +02:00
h44z
91d0155725 Fix build.sh and update script (#75) (#77)
* possible fix for #75

* Remove outdated bitbetter images as suggested by Ayitaka

* Fix exit status if no old images have been removed
2020-09-19 23:36:15 +01:00
h44z
f2b3b1babf Updates 1.34.0 (#69)
* Use latest release of bitwarden as base image (#67, #66)

* Add a script which simplifies Bitwarden updates

* fix typo

* Add UseApi

* Updated version, created update section

* Workaround for docker-compose --ignore-pull-failures bugs (4377 and 7127)

* use version from docker script

* check if bitbetter images are outdated

Co-authored-by: Lework <kuailemy123@163.com>
Co-authored-by: Jake Swenson <jakeswenson@users.noreply.github.com>
Co-authored-by: Jeff Alyanak <jeff@alyanak.ca>
2020-09-10 15:39:20 -04:00
captainhook
7d8f0b1082 Make Sso available in org license (#73)
* Exclude cert.cert from git

* Use latest release of bitwarden as base image (#67, #66)

* Add a script which simplifies Bitwarden updates

* fix typo

* Add UseApi

* Updated version, created update section

* Workaround for docker-compose --ignore-pull-failures bugs (4377 and 7127)

* use version from docker script

* check if bitbetter images are outdated

* Make Sso available in org license

Add `UseSso` var and enable

* Update README.md

Co-authored-by: Christoph Haas <christoph.h@sprinternet.at>
Co-authored-by: Lework <kuailemy123@163.com>
Co-authored-by: Captainhook <ec14018@qmul.ac.uk>
2020-09-10 15:36:44 -04:00
h44z
6527ac8362 Exclude cert.cert from git (#68) 2020-09-10 11:59:12 -07:00
accolon
faf7833f44 Make policies available in generated org licenses. (#56)
* Make policies available in generated org licenses.

* Change license version to current value used in BitWarden. Add info about compatibility in README file.
2020-03-17 12:19:18 +00:00
Michiel Hazelhof
006fa1fecf Update .net core and newtonsoft.json
Co-authored-by: Michiel Hazelhof <m.hazelhof@fyn.nl>

Due to compatibility issues laid out in issue #53:

Update from .net core 2.0 to 3.1 and newtonsoft.json 12.0.1 to 12.0.3.
2020-03-14 07:00:25 -04:00
Jan Schöppach
9c62a6f2ca Update README.md (#50)
* Update README.md

Added note that testing has been done up to Bitwarden 1.32.0.

Replaced editing of `docker-compose.yml` with the update-resistant usage
of `docker-compose.override.yml`
2020-02-27 13:05:29 -05:00
Jeff Alyanak
a2189a874e Updated Readme (#44)
* Updated Readme

Better description of dependencies, updated readme.

* Fixed default key directory

Script was not looking in the right place for the auto-generated key directory. This patch fixes issue #35.

* Cleans up Key Directory Patch

Key directory path fix was echoing the directory when run.
2020-01-18 14:33:03 -05:00
Jeff Alyanak
d98d4cad6a Fixed default key directory (#46)
* Fixed default key directory

Script was not looking in the right place for the auto-generated key directory. This patch fixes issue #35.

* Cleans up Key Directory Patch

Key directory path fix was echoing the directory when run.
2019-12-27 19:25:41 -05:00
Vinrobot
3e44d7347b Improve build and scripts (#30)
* Use absolute path rather than relative path in scripts

* Remove src/bitBetter/.keys/cert.cert

* Build licenseGen in Docker
This way we don't have to install dotnet sdk on the host

* Build bitBetter in Docker
This way we don't have to install dotnet sdk on the host

* Change DIR in run.sh to point to the project root

* Replace echo in Dockerfiles by set -x and set -e

* Use same Dockerfile for api and identity images

* Update README.md

* Update CircleCI config
The Docker Executor can't mount volume.
https://support.circleci.com/hc/en-us/articles/360007324514
https://circleci.com/docs/2.0/executor-types/#using-machine

* Make scripts work with sh

* Remove the container used to build bitBetter
2019-07-07 12:18:27 -04:00
Jan Schöppach
5d01d3c661 Fix recreation of keys on every execution (#27)
* Fix recreation of keys on every execution
2019-06-19 09:34:33 -04:00
Jeff Alyanak
6c8789cd67 License Generator Interactive Mode (#23)
* Added a Key Generating script

To make the keygen process a bit easier I've added a `generate-keys.sh` script that can be found in the `.keys` directory. It will generate the key & cert and bundle them into the required pkcs#12 file.

I've updated the readme to include instructions on the script.

* Generate bitbetter/identiry container with modified Core.dll

Added the generation of a second modified container, bitbetter/identity, which contains the modified dll. Fixes #12.

This works on my testing environment but has not gone through extensive testing. I'd recommend a review and cleanup of this commit before it is merged into the develop or master branches.

* Updated Docs

I've taken the steps written out by @online-stuff and consolidated/organized them into the README. This closes #13.

In a future update it might be worth adding a docs/ directory and breaking the readme into several docs that link to one another.

* Updated build.sh

Build now checks for and creates missing .keys directories.

* Added subj to allow for non-interactive use.

* Generate keys on build.

* Circle-ci needs to gen keys to test build

* Generate keys if they don't exist.

Don't overwrite if keys already exist.

* Generate keys online in the .keys directory

* Updated README.md

* Added initial interactive options

* Functional implementation of licensegen interactive mode.

* Bumped Newtonson.Json version

Never versions of the dotnet-sdk have issues with older Newtonsoft versions. 12.0.1 seems to satisfy the widest variety of sdk versions.

* Removing old readme

* Removed Duplicate Section

* Fixed typo

This fixes and closes issue #24.
2019-06-05 14:19:39 -04:00
Jeff Alyanak
15371c362c Bitbetter identity Core.dll (#14)
* Added a Key Generating script

To make the keygen process a bit easier I've added a `generate-keys.sh` script that can be found in the `.keys` directory. It will generate the key & cert and bundle them into the required pkcs#12 file automatically when running the build script if none already exist.

* Generate bitbetter/identiry container with modified Core.dll

Added the generation of a second modified container, bitbetter/identity, which contains the modified dll. Fixes #12.

This works on my testing environment but has not gone through extensive testing. I'd recommend a review and cleanup of this commit before it is merged into the develop or master branches.
2019-05-25 18:12:10 -04:00
Jeff Alyanak
5f85d83e45 Updated Docs (#15)
* Updated Docs

I've taken the steps written out by @online-stuff and consolidated/organized them into the README. This closes #13.

In a future update it might be worth adding a docs/ directory and breaking the readme into several docs that link to one another.

* Fixed Broken section Links
2019-05-20 22:21:10 -07:00
23 changed files with 1092 additions and 468 deletions

View File

@@ -1,15 +1,21 @@
version: 2 version: 2.1
jobs: jobs:
build: build:
docker: machine: true
- image: microsoft/dotnet:2-sdk
steps: steps:
- checkout - checkout
- run: - run:
name: Print the Current Time name: Print the Current Time
command: date command: date
- setup_remote_docker - run:
- run: { name: 'Get docker', command: 'curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh' } name: Generate Keys
command: ./generateKeys.sh
- run: - run:
name: Build script name: Build script
command: ./build.sh command: ./build.sh update
- run:
name: Test generating user license
command: ./licenseGen.sh user TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23
- run:
name: Test generating organization license
command: ./licenseGen.sh org TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23

19
.editorconfig Normal file
View File

@@ -0,0 +1,19 @@
root=true
###############################
# Core EditorConfig Options #
###############################
# All files
[*]
indent_style=tab
indent_size=4
trim_trailing_whitespace=true
end_of_line=lf
charset=utf-8
[*.{cs}]
insert_final_newline=false
[*.{md,mkdn}]
trim_trailing_whitespace = true
indent_style = space

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text eol=lf

5
.gitignore vendored
View File

@@ -1,7 +1,12 @@
.idea/ .idea/
bin/ bin/
obj/ obj/
src/licenseGen/.vs/*
src/bitBetter/.vs/*
*.dll *.dll
*.pem *.pem
.vscode/ .vscode/
*.pfx *.pfx
*.cer
*.vsidx
.DS_Store

View File

@@ -1,15 +0,0 @@
#!/bin/sh
# Check for openssl
command -v openssl >/dev/null 2>&1 || { echo >&2 "openssl required but not found. Aborting."; exit 1; }
# Remove any existing key files
[ ! -e cert.pem ] || rm cert.pem
[ ! -e key.pem ] || rm key.pem
[ ! -e cert.cert ] || rm cert.cert
[ ! -e cert.pfx ] || rm cert.pfx
# Generate new keys
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cert -days 36500 -outform DER -passout pass:test
openssl x509 -inform DER -in cert.cert -out cert.pem
openssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem -passin pass:test -passout pass:test

4
.servers/serverlist.txt Normal file
View File

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

200
README.md
View File

@@ -1,124 +1,174 @@
# BitBetter # BitBetter lite
BitBetter is is a tool to modify bitwardens 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. BitBetter is is a tool to modify Bitwarden's core dll to allow you to generate your own individual and organisation licenses.
_Beware! BitBetter does janky IL magic to rewrite the bitwarden core dll and install a self signed certificate. Use at your own risk!_ Please see the FAQ below for details on why this software was created.
Credit to https://github.com/h44z/BitBetter and https://github.com/jakeswenson/BitBetter Be aware that this branch is **only** for the lite (formerly unified) version of bitwarden. It has been rewritten and works in different ways than the master branch.
_Beware! BitBetter is a solution that generates a personal certificate and uses that to generate custom licences. This requires (automated) modifying of libraries. Use at your own risk!_
Credit to https://github.com/h44z/BitBetter and https://github.com/jakeswenson/BitBetter and https://github.com/GieltjE/BitBetter
# Table of Contents # Table of Contents
1. [Getting Started](#gettingstarted) - [BitBetter](#bitbetter)
+ [Pre-requisites](#prereq) - [Table of Contents](#table-of-contents)
+ [Setting up BitBetter](#setup) - [Getting Started](#getting-started)
+ [Building BitBetter](#building) - [Dependencies](#dependencies)
+ [Generating Signed Licenses](#generating) - [Setting up BitBetter](#setting-up-bitbetter)
2. [FAQ](#faq) - [Optional: Manually generating Certificate & Key](#optional-manually-generating-certificate--key)
3. [Footnotes](#footnotes) - [Building BitBetter](#building-bitbetter)
- [Updating Bitwarden and BitBetter](#updating-bitwarden-and-bitbetter)
- [Generating Signed Licenses](#generating-signed-licenses)
- [Note: Alternative Ways to Generate License](#note-alternative-ways-to-generate-license)
- [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)
- [Shouldn't you have reached out to Bitwarden to ask them for alternative licensing structures?](#shouldnt-you-have-reached-out-to-bitwarden-to-ask-them-for-alternative-licensing-structures)
- [Footnotes](#footnotes)
# Getting Started <a name=#gettingstarted></a> # 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).
## Pre-requisites <a name=#prereq></a> ## 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:
* openssl (probably already installed on most Linux or WSL systems) * Bitwarden (tested with 2025.11.1 might work on lower versions), for safety always stay up to date
* dotnet-sdk-2.1 (install instructions can be found [here](https://dotnet.microsoft.com/download/linux-package-manager/rhel/sdk-2.1.604)) * 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 <a name=#setup></a> ## Setting up BitBetter
With your pre-requisites 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/online-stuff/BitBetter.git git clone https://github.com/jakeswenson/BitBetter.git
``` ```
First, we need to add the correct version of Newtonsoft.Json to the license generator and the BitBetter docker directories. ### Optional: Manually generating Certificate & Key
If you wish to generate your self-signed cert & key manually, you can run the following commands.
```bash ```bash
cd BitBetter/src/licenseGen/ cd .keys
dotnet add package Newtonsoft.Json --version 12.0.1 openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cer -days 36500 -outform DER -passout pass:test
openssl x509 -inform DER -in cert.cer -out cert.pem
cd ../bitBetter
dotnet add package Newtonsoft.Json --version 12.0.1
```
Next, we need to generate the self-signed certificate we will use to sign any licenses we generate.
To sign your own license you first need to generate your own signing cert using the `.keys/generate-keys.sh` script.
Running this script will prompt you to enter some information about your new certificate, you may leave these at the defaults or set them to your preference. The script will then create a pkcs12 file (.pfx) containing your new key/cert.
You may also choose to do this manually via the following commands.
```bash
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cert -days 36500 -outform DER -passout pass:test
openssl x509 -inform DER -in cert.cert -out cert.pem
openssl 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>[1](#f1)</sup>
---
## Building BitBetter <a name=#building></a>
Now that you've generated your own own self-signed certificate, you can run the main `BitBetter/build.sh` script to generate a modified version of the `bitwarden/api` docker image. ## Building BitBetter
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 `ghcr.io/bitwarden/lite` 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 modified version of the official `bitwarden/api` called `bitbetter/api`. You may now simply edit your bitwarden docker-compose.yml to utilize the modified image. This will create a new self-signed certificate in the `.keys` directory if one does not already exist and then create a modified version of the official `ghcr.io/bitwarden/lite` image called `bitwarden-patched`.
Edit your `/path/to/bwdata/docker/docker-compose.yml`. Afterwards it will automatically generate the license generator and start all previously specified containers which are **now ready to accept self-issued licenses.**
> Replace `image: bitwarden/api:x.xx.x`<br>with `image: bitbetter/api`
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` ## Updating Bitwarden and BitBetter
You can now start or restart Bitwarden as normal and the modified api will be used. <b>It is now ready to accept self-issued licenses.</b> 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 <a name=#generating></a> ## 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. 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.
First, from the `BitBetter/src/licenseGen` directory, build the license generator.<sup>[2](#f2)</sup> 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/).
```bash **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.**
./build.sh
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
``` ```
Now, from the `BitBetter/src/licenseGen` directory, you can run the tool to generate licenses. **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!**
You'll need to get a user's <b>GUID</b> in order to generate an <b>invididual license</b> and the server's <b>install ID</b> to generate an <b>Organization license</b>. These can be retrieved most easily through the Bitwarden [Admin Portal](https://help.bitwarden.com/article/admin-portal/).
```bash ## Migrating from mssql to a real database
./run.sh ~/BitBetter/.keys/cert.pfx user "Name" "EMail" "User-GUID"
./run.sh ~/BitBetter/.keys/cert.pfx org "Name" "EMail" "Install-ID used to install the server" Prepare a new database and bwdata directory, download and prepare the new settings.env (https://raw.githubusercontent.com/bitwarden/self-host/refs/heads/main/bitwarden-lite/settings.env)
Make sure you can get the data from either the backup file or by connecting directly to the mssql database (navicat has a trial).
If required (e.g. you cannot connect to your docker mssql server directly) download Microsoft SQL Server 2022 and SQL Server Management Studio (the latter can be used to import the .bak file)
After cloning this repo and modifying .servers/serverlist.txt to suit your new environment do the following:
```
docker exec -i bitwarden-mssql /backup-db.sh
./bitwarden.sh stop
``` ```
<b>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!</b> Run build.sh and ensure your new instance serves a webpage AND has populated the new database with the tables (should be empty now)
# FAQ: Questions (you might have?) <a name=#faq></a> Proceed to stop the new container for now.
I'll work on updates in the next couple weeks, right now, I just wanted something to start with. Copy from the old to the new bwdata directory (do not copy/overwrite identity.pfx!):
- bwdata/core/licenses to bwdata-new/licenses
- bwdata/core/aspnet-dataprotection to bwdata-new/data-protection
- bwdata/core/attachments to bwdata-new/attachments
## But why? Its open source? Export data only from the old sql server database, if needed import the .bak file to a local mssql instance.
Yes, bitwarden is great. If I didn't care about it i wouldn't be doing this. Only export tables that have rows, makes it much quicker, .json is the easiest with navicat.
I was bothered that if i want to host bitwarden myself, at my house,
for my family to use (with the ability to share access) I would still have to pay a monthly ENTERPRISE organization fee.
To host it myself. And maintain it myself. Basically WTH was bitwarden doing that I was paying them for?
## You should have reached out to bitwarden Import the rows to the real database, start the new docker container.
Thanks, good idea. And I did. Currently they're not focused on solving this issue - yet. ---
To be clear i'm totally happy to give them my money. Offer a perpetual server license, and i'd pay for it. Let me license the server, period. Allow an orginzation to have Premium for all users.. 500 seats, let the 500 users in the orginzation have the Premium features too.
I'm still in the testing/evaluating phase. If I am hosting the server/data, let me license the server, period. How many licenses does one user need to have... # FAQ: Questions you might have.
# Footnotes <a name=#footnotes></a> ## Why build a license generator for open source software?
<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 it's `GenerateUserLicense` and `GenerateOrgLicense` calls, but this is really unnecessary as this certificate does not represent any type of security issue. We agree that Bitwarden is great. If we didn't care about it then we wouldn't be doing this. We believe that if a user wants to host Bitwarden themselves, in their house, for their family to use and with the ability to share access, they would still have to pay a **monthly** enterprise organization fee. When hosting and maintaining the software yourself there is no need to pay for the level of service that an enterprise customer needs.
<a name="#f2"><sup>2</sup></a>This tool build ontop of the `bitbetter/api` container image so make sure you've built that above using the root `./build.sh` script. Unfortunately, Bitwarden doesn't seem to have any method for receiving donations so we recommend making a one-time donation to your open source project of choice for each BitBetter license you generate if you can afford to do so.
## Shouldn't you have reached out to Bitwarden to ask them for alternative licensing structures?
In the past we have done so but they were not focused on the type of customer that would want a one-time license and would be happy to sacrifice customer service. We believe the features that are currently behind this subscription paywall to be critical ones and believe they should be available to users who can't afford an enterprise payment structure. We'd even be happy to see a move towards a Gitlab-like model where premium features are rolled out *first* to the enterprise subscribers before being added to the fully free version.
UPDATE: Bitwarden now offers a cheap license called [Families Organization](https://bitwarden.com/pricing/) that provides premium features and the ability to self-host Bitwarden for six persons.
## 2fa doesn't work
Unfortunately the new BitWarden container doesn't set the timezone and ignores TZ= from the environment, can be fixed by:
```
docker exec bitwarden ln -s /usr/share/zoneinfo/Europe/Amsterdam /etc/localtime
```
## Changes in settings.env
Require a recreation of the docker container, build.sh will suffice too.
## Migrating from the old unified branch
```
git branch -m unified lite
git fetch origin
git branch -u origin/lite lite
git remote set-head origin -a
```
# Footnotes
<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> 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.

132
build.ps1 Normal file
View File

@@ -0,0 +1,132 @@
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
# detect buildx, ErrorActionPreference will ensure the script stops execution if not found
docker buildx version
# Enable BuildKit for better build experience and to ensure platform args are populated
$env:DOCKER_BUILDKIT=1
$env:COMPOSE_DOCKER_CLI_BUILD=1
# define temporary directory
$tempdirectory = "$pwd\temp"
# define services to patch
$components = "Api","Identity"
# delete old directories / files if applicable
if (Test-Path "$tempdirectory" -PathType Container) {
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.cer" -PathType Leaf) {
Remove-Item "$pwd\src\bitBetter\cert.cer" -Force
}
if (Test-Path "$pwd\.keys\cert.cert" -PathType Leaf) {
Rename-Item -Path "$pwd\.keys\cert.cert" -NewName "$pwd\.keys\cert.cer"
}
# generate keys if none are available
if (!(Test-Path "$pwd\.keys" -PathType Container)) {
.\generateKeys.ps1
}
# copy the key to bitBetter
Copy-Item "$pwd\.keys\cert.cer" -Destination "$pwd\src\bitBetter"
# build bitBetter and clean the source directory after
docker build --no-cache -t bitbetter/bitbetter "$pwd\src\bitBetter"
Remove-Item "$pwd\src\bitBetter\cert.cer" -Force
# gather all running instances, cannot run a wildcard filter on Ancestor= :(, does find all where name = *bitwarden*
$oldinstances = docker container ps --all -f Name=bitwarden --format '{{.ID}}'
# stop and remove all running instances
foreach ($instance in $oldinstances) {
docker stop $instance
docker rm $instance
}
# update bitwarden itself
if ($args[0] -eq 'update') {
docker pull ghcr.io/bitwarden/lite:latest
} else {
$confirmation = Read-Host "Update (or get) bitwarden source container (y/n)"
if ($confirmation -eq 'y') {
docker pull ghcr.io/bitwarden/lite:latest
}
}
# stop and remove previous existing patch(ed) container
$oldinstances = docker container ps --all -f Ancestor=bitwarden-patched --format '{{.ID}}'
foreach ($instance in $oldinstances) {
docker stop $instance
docker rm $instance
}
$oldinstances = docker image ls bitwarden-patched --format '{{.ID}}'
foreach ($instance in $oldinstances) {
docker image rm $instance
}
# remove old extract containers
$oldinstances = docker container ps --all -f Name=bitwarden-extract --format '{{.ID}}'
foreach ($instance in $oldinstances) {
docker stop $instance
docker rm $instance
}
# start a new bitwarden instance so we can patch it
$patchinstance = docker run -d --name bitwarden-extract ghcr.io/bitwarden/lite:latest
# 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"
}
# stop and remove our temporary container
docker stop bitwarden-extract
docker rm bitwarden-extract
# run bitBetter, this applies our patches to the required files
docker run -v "$tempdirectory`:/app/mount" --rm bitbetter/bitbetter
# create a new image with the patched files
docker build . --tag bitwarden-patched --file "$pwd\src\bitBetter\Dockerfile-bitwarden-patch"
# start all user requested instances
if (Test-Path -Path "$pwd\.servers\serverlist.txt" -PathType Leaf) {
foreach($line in Get-Content "$pwd\.servers\serverlist.txt") {
if (!($line.StartsWith("#"))) {
Invoke-Expression "& $line"
}
}
}
# remove our bitBetter image
docker image rm bitbetter/bitbetter
# copy our patched library to the licenseGen source directory
Copy-Item "$tempdirectory\Identity\Core.dll" -Destination "$pwd\src\licenseGen"
Copy-Item "$pwd\.keys\cert.pfx" -Destination "$pwd\src\licenseGen"
# build the licenseGen
docker build -t bitbetter/licensegen "$pwd\src\licenseGen"
# clean the licenseGen source directory
Remove-Item "$pwd\src\licenseGen\Core.dll" -Force
Remove-Item "$pwd\src\licenseGen\cert.pfx" -Force
# remove our temporary directory
Remove-Item "$tempdirectory" -Recurse -Force

134
build.sh
View File

@@ -1,13 +1,135 @@
#!/bin/bash #!/bin/bash
set -e
mkdir ./src/bitBetter/.keys # detect buildx, set -e will ensure the script stops execution if not found
docker buildx version
cp .keys/cert.cert ./src/bitBetter/.keys # Enable BuildKit for better build experience and to ensure platform args are populated
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1
cd ./src/bitBetter # define temporary directory
TEMPDIRECTORY="$PWD/temp"
dotnet restore # define services to patch
dotnet publish COMPONENTS=("Api" "Identity")
docker build --pull . -t bitbetter/api # --squash # delete old directories / files if applicable
if [ -d "$TEMPDIRECTORY" ]; then
rm -rf "$TEMPDIRECTORY"
fi
if [ -f "$PWD/src/licenseGen/Core.dll" ]; then
rm -f "$PWD/src/licenseGen/Core.dll"
fi
if [ -f "$PWD/src/licenseGen/cert.pfx" ]; then
rm -f "$PWD/src/licenseGen/cert.pfx"
fi
if [ -f "$PWD/src/bitBetter/cert.cer" ]; then
rm -f "$PWD/src/bitBetter/cert.cer"
fi
if [ -f "$PWD/.keys/cert.cert" ]; then
mv "$PWD/.keys/cert.cert" "$PWD/.keys/cert.cer"
fi
# generate keys if none are available
if [ ! -d "$PWD/.keys" ]; then
./generateKeys.sh
fi
# copy the key to bitBetter
cp -f "$PWD/.keys/cert.cer" "$PWD/src/bitBetter"
# build bitBetter and clean the source directory after
docker build --no-cache -t bitbetter/bitbetter "$PWD/src/bitBetter"
rm -f "$PWD/src/bitBetter/cert.cer"
# gather all running instances, cannot run a wildcard filter on Ancestor= :(, does find all where name = *bitwarden*
OLDINSTANCES=$(docker container ps --all -f Name=bitwarden --format '{{.ID}}')
# stop and remove all running instances
for INSTANCE in ${OLDINSTANCES[@]}; do
docker stop $INSTANCE
docker rm $INSTANCE
done
# update bitwarden itself
if [ "$1" = "update" ]; then
docker pull ghcr.io/bitwarden/lite:latest
else
read -p "Update (or get) bitwarden source container (y/n): "
if [[ $REPLY =~ ^[Yy]$ ]]; then
docker pull ghcr.io/bitwarden/lite:latest
fi
fi
# stop and remove previous existing patch(ed) container
OLDINSTANCES=$(docker container ps --all -f Ancestor=bitwarden-patched --format '{{.ID}}')
for INSTANCE in ${OLDINSTANCES[@]}; do
docker stop $INSTANCE
docker rm $INSTANCE
done
OLDINSTANCES=$(docker image ls bitwarden-patched --format '{{.ID}}')
for INSTANCE in ${OLDINSTANCES[@]}; do
docker image rm $INSTANCE
done
# remove old extract containers
OLDINSTANCES=$(docker container ps --all -f Name=bitwarden-extract --format '{{.ID}}')
for INSTANCE in ${OLDINSTANCES[@]}; do
docker stop $INSTANCE
docker rm $INSTANCE
done
# start a new bitwarden instance so we can patch it
PATCHINSTANCE=$(docker run -d --name bitwarden-extract ghcr.io/bitwarden/lite:latest)
# 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
# stop and remove our temporary container
docker stop bitwarden-extract
docker rm bitwarden-extract
# run bitBetter, this applies our patches to the required files
docker run -v "$TEMPDIRECTORY:/app/mount" --rm bitbetter/bitbetter
# create a new image with the patched files
docker build . --tag bitwarden-patched --file "$PWD/src/bitBetter/Dockerfile-bitwarden-patch"
# start all user requested instances
if [ -f "$PWD/.servers/serverlist.txt" ]; then
# convert line endings to unix
sed -i 's/\r$//' "$PWD/.servers/serverlist.txt"
cat "$PWD/.servers/serverlist.txt" | while read -r LINE; do
if [[ $LINE != "#"* ]]; then
bash -c "$LINE"
fi
done
fi
# remove our bitBetter image
docker image rm bitbetter/bitbetter
# copy our patched library to the licenseGen source directory
cp -f "$TEMPDIRECTORY/Identity/Core.dll" "$PWD/src/licenseGen"
cp -f "$PWD/.keys/cert.pfx" "$PWD/src/licenseGen"
# build the licenseGen
docker build -t bitbetter/licensegen "$PWD/src/licenseGen"
# clean the licenseGen source directory
rm -f "$PWD/src/licenseGen/Core.dll"
rm -f "$PWD/src/licenseGen/cert.pfx"
# remove our temporary directory
rm -rf "$TEMPDIRECTORY"

25
generateKeys.ps1 Normal file
View File

@@ -0,0 +1,25 @@
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
# 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.cer`" -days 36500 -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -outform DER -passout pass:test"
Invoke-Expression "& '$opensslbinary' x509 -inform DER -in `"$pwd\.keys\cert.cer`" -out `"$pwd\.keys\cert.pem`""
Invoke-Expression "& '$opensslbinary' pkcs12 -export -out `"$pwd\.keys\cert.pfx`" -inkey `"$pwd\.keys\key.pem`" -in `"$pwd\.keys\cert.pem`" -passin pass:test -passout pass:test"

20
generateKeys.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
set -e
# Check for openssl
command -v openssl >/dev/null 2>&1 || { echo >&2 "openssl required but not found. Aborting."; exit 1; }
DIR="$PWD/.keys"
# if previous keys exist, remove them
if [ -d "$DIR" ]; then
rm -rf "$DIR"
fi
# create new directory
mkdir "$DIR"
# Generate new keys
openssl req -x509 -newkey rsa:4096 -keyout "$DIR/key.pem" -out "$DIR/cert.cer" -days 36500 -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -outform DER -passout pass:test
openssl x509 -inform DER -in "$DIR/cert.cer" -out "$DIR/cert.pem"
openssl pkcs12 -export -out "$DIR/cert.pfx" -inkey "$DIR/key.pem" -in "$DIR/cert.pem" -passin pass:test -passout pass:test

17
licenseGen.ps1 Normal file
View File

@@ -0,0 +1,17 @@
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
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] -eq "interactive") {
docker run -it --rm bitbetter/licensegen interactive
} else {
docker run bitbetter/licensegen $args
}

17
licenseGen.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/bash
set -e
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

Binary file not shown.

View File

@@ -1,12 +1,14 @@
FROM bitwarden/api FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /bitBetter
COPY bin/Debug/netcoreapp2.0/publish/* /bitBetter/ COPY . /bitBetter
COPY ./.keys/cert.cert /newLicensing.cer COPY cert.cer /app/
RUN dotnet /bitBetter/bitBetter.dll && \ RUN dotnet restore
echo "modified 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:8.0
echo "replaced dll" && \ WORKDIR /app
rm -rf /bitBetter && rm -rf /newLicensing.cer && \ COPY --from=build /app .
echo "cleaned up"
ENTRYPOINT ["dotnet", "/app/bitBetter.dll"]

View File

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

View File

@@ -1,93 +1,67 @@
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()
{ {
static int Main(string[] args) const String certFile = "/app/cert.cer";
{ String[] files = Directory.GetFiles("/app/mount", "Core.dll", SearchOption.AllDirectories);
string cerFile;
string corePath;
if(args.Length >= 2) { foreach (String file in files)
cerFile = args[0]; {
corePath = args[1]; Console.WriteLine(file);
} else if (args.Length == 1) { ModuleDefMD moduleDefMd = ModuleDefMD.Load(file);
cerFile = args[0]; Byte[] cert = File.ReadAllBytes(certFile);
corePath = "/app/Core.dll";
}
else {
cerFile = "/newLicensing.cer";
corePath = "/app/Core.dll";
}
EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources.OfType<EmbeddedResource>().First(r => r.Name.Equals("Bit.Core.licensing.cer"));
EmbeddedResource embeddedResourceToAdd = new("Bit.Core.licensing.cer", cert) { Attributes = embeddedResourceToRemove.Attributes };
moduleDefMd.Resources.Add(embeddedResourceToAdd);
moduleDefMd.Resources.Remove(embeddedResourceToRemove);
var module = ModuleDefinition.ReadModule(new MemoryStream(File.ReadAllBytes(corePath))); DataReader reader = embeddedResourceToRemove.CreateReader();
var cert = File.ReadAllBytes(cerFile); X509Certificate2 existingCert = new(reader.ReadRemainingBytes());
var x = module.Resources.OfType<EmbeddedResource>() Console.WriteLine($"Existing Cert Thumbprint: {existingCert.Thumbprint}");
.Where(r => r.Name.Equals("Bit.Core.licensing.cer")) X509Certificate2 certificate = new(cert);
.First();
Console.WriteLine(x.Name); Console.WriteLine($"New Cert Thumbprint: {certificate.Thumbprint}");
var e = new EmbeddedResource("Bit.Core.licensing.cer", x.Attributes, cert); IEnumerable<TypeDef> services = moduleDefMd.Types.Where(t => t.Namespace == "Bit.Core.Billing.Services");
TypeDef type = services.First(t => t.Name == "LicensingService");
MethodDef constructor = type.FindConstructors().First();
module.Resources.Add(e); Instruction instructionToPatch = constructor.Body.Instructions.FirstOrDefault(i => i.OpCode == OpCodes.Ldstr && String.Equals((String)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase));
module.Resources.Remove(x);
var services = module.Types.Where(t => t.Namespace == "Bit.Core.Services"); if (instructionToPatch != null)
{
instructionToPatch.Operand = certificate.Thumbprint;
}
else
{
Console.WriteLine("Can't find constructor to patch");
}
ModuleWriterOptions moduleWriterOptions = new(moduleDefMd);
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.KeepOldMaxStack;
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveAll;
moduleWriterOptions.MetadataOptions.Flags |= MetadataFlags.PreserveRids;
var type = services.First(t => t.Name == "LicensingService"); moduleDefMd.Write(file + ".new");
moduleDefMd.Dispose();
File.Delete(file);
File.Move(file + ".new", file);
}
var licensingType = type.Resolve(); return 0;
}
var existingCert = new X509Certificate2(x.GetResourceData());
Console.WriteLine($"Existing Cert Thumbprin: {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;
}
}
} }

View File

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

View File

@@ -1,5 +1,15 @@
FROM bitbetter/api FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /licenseGen
COPY bin/Debug/netcoreapp2.0/publish/* /app/ COPY . /licenseGen
COPY Core.dll /app/
COPY cert.pfx /app/
ENTRYPOINT [ "dotnet", "/app/licenseGen.dll", "--core", "/app/Core.dll", "--cert", "/cert.pfx" ] RUN dotnet restore
RUN dotnet publish -c Release -o /app --no-restore
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "/app/licenseGen.dll", "--cert=/app/cert.pfx", "--core=/app/Core.dll"]

View File

@@ -1,223 +1,485 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Text.Json;
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 McMaster.Extensions.CommandLineUtils;
using Newtonsoft.Json;
namespace bitwardenSelfLicensor namespace licenseGen;
internal class Program
{ {
class Program private static readonly CommandLineApplication App = new();
{ private static readonly CommandOption Cert = App.Option("--cert", "Certificate file", CommandOptionType.SingleValue);
static int Main(string[] args) private static readonly CommandOption CoreDll = App.Option("--core", "Path to Core.dll", CommandOptionType.SingleValue);
{
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() private static Int32 Main(String[] args)
{ {
return File.Exists(cert.Value()); App.Command("interactive", config =>
} {
String buff, licenseType = "", name = "", email = "", businessName="";
Int16 storage = 0;
Boolean validGuid = false, validInstallid = false;
Guid guid = Guid.Empty, installid = Guid.Empty;
bool coreExists() config.OnExecute(() =>
{ {
return File.Exists(coreDll.Value()); Check();
} Console.WriteLine("Interactive license mode...");
bool verifyTopOptions() while (licenseType == "")
{ {
return !string.IsNullOrWhiteSpace(cert.Value()) && Console.WriteLine("What would you like to generate, a [u]ser license or an [o]rg license: ");
!string.IsNullOrWhiteSpace(coreDll.Value()) && buff = Console.ReadLine();
certExists() && coreExists();
}
app.Command("user", config => switch (buff)
{ {
var name = config.Argument("Name", "your name"); case "u":
var email = config.Argument("Email", "your email"); {
var userIdArg = config.Argument("User ID", "your user id"); licenseType = "user";
var key = config.Argument("Key", "your key id (optional)"); Console.WriteLine("Okay, we will generate a user license.");
var help = config.HelpOption("--help | -h | -?");
config.OnExecute(() => while (!validGuid)
{ {
if (!verifyTopOptions()) Console.WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]: ");
{ buff = Console.ReadLine();
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(); if (Guid.TryParse(buff, out guid))validGuid = true;
return 1; else Console.WriteLine("The user-guid provided does not appear to be valid!");
} }
else if (string.IsNullOrWhiteSpace(name.Value) || string.IsNullOrWhiteSpace(email.Value)) break;
{ }
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}'"); case "o":
config.ShowHelp("user"); {
return 1; licenseType = "org";
} Console.WriteLine("Okay, we will generate an organization license.");
while (!validInstallid)
{
Console.WriteLine("Please provide your Bitwarden Install-ID — refer to the Readme for details on how to retrieve this. [Install-ID]: ");
buff = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userIdArg.Value) || !Guid.TryParse(userIdArg.Value, out Guid userId)) if (Guid.TryParse(buff, out installid)) validInstallid = true;
{ else Console.WriteLine("The install-id provided does not appear to be valid.");
config.Error.WriteLine($"User ID not provided"); }
config.ShowHelp("user");
return 1;
}
GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name.Value, email.Value, userId, key.Value); while (businessName == "")
{
Console.WriteLine("Please enter a business name, default is BitBetter. [Business Name]: ");
buff = Console.ReadLine();
if (buff == "")
{
businessName = "BitBetter";
}
else if (CheckBusinessName(buff))
{
businessName = buff;
}
}
break;
}
default:
Console.WriteLine("Unrecognized option \'" + buff + "\'.");
break;
}
}
return 0; while (name == "")
}); {
}); Console.WriteLine("Please provide the username this license will be registered to. [username]: ");
app.Command("org", config => buff = Console.ReadLine();
{ if (CheckUsername(buff)) name = buff;
var name = config.Argument("Name", "your name"); }
var email = config.Argument("Email", "your email");
var installId = config.Argument("InstallId", "your installation id (GUID)");
var key = config.Argument("Key", "your key id (optional)");
var help = config.HelpOption("--help | -h | -?");
config.OnExecute(() => while (email == "")
{ {
if (!verifyTopOptions()) Console.WriteLine("Please provide the email address for the user " + name + ". [email]: ");
{ buff = Console.ReadLine();
if (!coreExists()) if (CheckEmail(buff))
{ {
config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); email = buff;
} }
if (!certExists()) }
{
config.Error.WriteLine($"Cant find certificate at: {cert.Value()}");
}
config.ShowHelp(); while (storage == 0)
return 1; {
} Console.WriteLine("Extra storage space for the user " + name + ". (max.: " + Int16.MaxValue + "). Defaults to maximum value. [storage]");
else if (string.IsNullOrWhiteSpace(name.Value) || buff = Console.ReadLine();
string.IsNullOrWhiteSpace(email.Value) || if (String.IsNullOrWhiteSpace(buff))
string.IsNullOrWhiteSpace(installId.Value)) {
{ storage = Int16.MaxValue;
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}' InstallId='{installId.Value}'"); }
config.ShowHelp("org"); else
return 1; {
} if (CheckStorage(buff))
{
storage = Int16.Parse(buff);
}
}
}
if (!Guid.TryParse(installId.Value, out Guid installationId)) switch (licenseType)
{ {
config.Error.WriteLine("Unable to parse your installation id as a GUID"); case "user":
config.Error.WriteLine($"Here's a new guid: {Guid.NewGuid()}"); {
config.ShowHelp("org"); Console.WriteLine("Confirm creation of \"user\" license for username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", User-GUID: \"" + guid + "\"? Y/n");
return 1; buff = Console.ReadLine();
} if (buff is "" or "y" or "Y")
{
GenerateUserLicense(new X509Certificate2(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, guid, null);
}
else
{
Console.WriteLine("Exiting...");
return 0;
}
GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name.Value, email.Value, installationId, key.Value); break;
}
case "org":
{
Console.WriteLine("Confirm creation of \"organization\" license for business name: \"" + businessName + "\", username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", Install-ID: \"" + installid + "\"? Y/n");
buff = Console.ReadLine();
if (buff is "" or "y" or "Y")
{
GenerateOrgLicense(new X509Certificate2(Cert.Value(), "test"), CoreDll.Value(), name, email, storage, installid, businessName, null);
}
else
{
Console.WriteLine("Exiting...");
return 0;
}
return 0; break;
}); }
}); }
app.OnExecute(() => return 0;
{ });
app.ShowHelp(); });
return 10; 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)");
app.HelpOption("-? | -h | --help"); config.OnExecute(() =>
{
Check();
try if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value))
{ {
return app.Execute(args); config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}'");
} config.ShowHelp(true);
catch (Exception e) return 1;
{ }
Console.Error.WriteLine("Oops: {0}", e);
return 100;
}
}
static void GenerateUserLicense(X509Certificate2 cert, string corePath, if (String.IsNullOrWhiteSpace(userIdArg.Value) || !Guid.TryParse(userIdArg.Value, out Guid userId))
string userName, string email, Guid userId, string key) {
{ config.Error.WriteLine("User ID not provided");
var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); config.ShowHelp(true);
return 1;
}
var type = core.GetType("Bit.Core.Models.Business.UserLicense"); Int16 storageShort = 0;
if (!String.IsNullOrWhiteSpace(storage.Value))
{
Double parsedStorage = Double.Parse(storage.Value);
if (parsedStorage is > Int16.MaxValue or < 0)
{
config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]");
config.ShowHelp(true);
return 1;
}
storageShort = (Int16) parsedStorage;
}
var license = Activator.CreateInstance(type); GenerateUserLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value);
void set(string name, object value) return 0;
{ });
type.GetProperty(name).SetValue(license, value); });
} 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)");
set("LicenseKey", string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); config.OnExecute(() =>
set("Id", userId); {
set("Name", userName); Check();
set("Email", email);
set("MaxStorageGb", short.MaxValue);
set("Premium", true);
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("Hash", Convert.ToBase64String((byte[])type.GetMethod("ComputeHash").Invoke(license, new object[0]))); if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value) || String.IsNullOrWhiteSpace(installId.Value))
set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert }))); {
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}' InstallId='{installId.Value}'");
config.ShowHelp(true);
return 1;
}
Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); 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(true);
return 1;
}
static void GenerateOrgLicense(X509Certificate2 cert, string corePath, Int16 storageShort = 0;
string userName, string email, Guid instalId, string key) if (!String.IsNullOrWhiteSpace(storage.Value))
{ {
var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); Double parsedStorage = Double.Parse(storage.Value);
if (parsedStorage is > Int16.MaxValue or < 0)
{
config.Error.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]");
config.ShowHelp(true);
return 1;
}
storageShort = (Int16)parsedStorage;
}
var type = core.GetType("Bit.Core.Models.Business.OrganizationLicense"); GenerateOrgLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value);
var license = Activator.CreateInstance(type); return 0;
});
});
void set(string name, object value) App.OnExecute(() =>
{ {
type.GetProperty(name).SetValue(license, value); App.ShowHelp();
} return 10;
});
set("LicenseKey", string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); try
set("InstallationId", instalId); {
set("Id", Guid.NewGuid()); App.HelpOption("-? | -h | --help");
set("Name", userName); return App.Execute(args);
set("BillingEmail", email); }
set("BusinessName", "BitBetter"); catch (Exception exception)
set("Enabled", true); {
set("Plan", "Custom"); Console.Error.WriteLine("Oops: {0}", exception);
set("PlanType", (byte)6); return 100;
set("Seats", (short)32767); }
set("MaxCollections", short.MaxValue); }
set("UseGroups", true);
set("UseEvents", true);
set("UseDirectory", true);
set("UseTotp", true);
set("Use2fa", true);
set("MaxStorageGb", short.MaxValue);
set("SelfHost", true);
set("UsersGetPremium", true);
set("Version", 4);
set("Issued", DateTime.UtcNow);
set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
set("Expires", DateTime.UtcNow.AddYears(100));
set("Trial", false);
set("Hash", Convert.ToBase64String((byte[])type.GetMethod("ComputeHash").Invoke(license, new object[0]))); private static void Check()
set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert }))); {
if (Cert == null || String.IsNullOrWhiteSpace(Cert.Value()))
{
App.Error.WriteLine("No certificate specified");
App.ShowHelp();
Environment.Exit(1);
}
else if (CoreDll == null || String.IsNullOrWhiteSpace(CoreDll.Value()))
{
App.Error.WriteLine("No core dll specified");
App.ShowHelp();
Environment.Exit(1);
}
else if (!File.Exists(Cert.Value()))
{
App.Error.WriteLine($"Can't find certificate at: {Cert.Value()}");
App.ShowHelp();
Environment.Exit(1);
}
else if (!File.Exists(CoreDll.Value()))
{
App.Error.WriteLine($"Can't find core dll at: {CoreDll.Value()}");
App.ShowHelp();
Environment.Exit(1);
}
}
Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); // checkUsername Checks that the username is a valid username
} private static Boolean CheckUsername(String s)
} {
// TODO: Actually validate
if (!String.IsNullOrWhiteSpace(s)) return true;
Console.WriteLine("The username provided doesn't appear to be valid!");
return false;
}
// checkBusinessName Checks that the Business Name is a valid username
private static Boolean CheckBusinessName(String s)
{
// TODO: Actually validate
if (!String.IsNullOrWhiteSpace(s)) return true;
Console.WriteLine("The Business Name provided doesn't appear to be valid!");
return false;
}
// checkEmail Checks that the email address is a valid email address
private static Boolean CheckEmail(String s)
{
// TODO: Actually validate
if (!String.IsNullOrWhiteSpace(s)) return true;
Console.WriteLine("The email provided doesn't appear to be valid!");
return false;
}
// checkStorage Checks that the storage is in a valid range
private static Boolean CheckStorage(String s)
{
if (String.IsNullOrWhiteSpace(s))
{
Console.WriteLine("The storage provided doesn't appear to be valid!");
return false;
}
if (!(Double.Parse(s) > Int16.MaxValue) && !(Double.Parse(s) < 0)) return true;
Console.WriteLine("The storage value provided is outside the accepted range of [0-" + Int16.MaxValue + "]!");
return false;
}
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
private static void GenerateUserLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid userId, String key)
{
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath));
Type type = core.GetType("Bit.Core.Billing.Models.Business.UserLicense");
Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType");
if (type == null)
{
Console.WriteLine("Could not find type!");
return;
}
if (licenseTypeEnum == null)
{
Console.WriteLine("Could not find license licenseTypeEnum!");
return;
}
Object license = Activator.CreateInstance(type);
MethodInfo computeHash = type.GetMethod("ComputeHash");
if (computeHash == null)
{
Console.WriteLine("Could not find ComputeHash!");
return;
}
MethodInfo sign = type.GetMethod("Sign");
if (sign == null)
{
Console.WriteLine("Could not find sign!");
return;
}
Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
Set(type, license, "Id", userId);
Set(type, license, "Name", userName);
Set(type, license, "Email", email);
Set(type, license, "Premium", true);
Set(type, license, "MaxStorageGb", storage == 0 ? Int16.MaxValue : storage);
Set(type, license, "Version", 1);
Set(type, license, "Issued", DateTime.UtcNow);
Set(type, license, "Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
Set(type, license, "Expires", DateTime.UtcNow.AddYears(100));
Set(type, license, "Trial", false);
Set(type, license, "LicenseType", Enum.Parse(licenseTypeEnum, "User"));
Set(type, license, "Hash", Convert.ToBase64String(((Byte[])computeHash.Invoke(license, []))!));
Set(type, license, "Signature", Convert.ToBase64String((Byte[])sign.Invoke(license, [cert])!));
Console.WriteLine(JsonSerializer.Serialize(license, JsonOptions));
}
private static void GenerateOrgLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid instalId, String businessName, String key)
{
Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath));
Type type = core.GetType("Bit.Core.Billing.Organizations.Models.OrganizationLicense");
Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType");
Type planTypeEnum = core.GetType("Bit.Core.Billing.Enums.PlanType");
if (type == null)
{
Console.WriteLine("Could not find type!");
return;
}
if (licenseTypeEnum == null)
{
Console.WriteLine("Could not find licenseTypeEnum!");
return;
}
if (planTypeEnum == null)
{
Console.WriteLine("Could not find planTypeEnum!");
return;
}
Object license = Activator.CreateInstance(type);
MethodInfo computeHash = type.GetMethod("ComputeHash");
if (computeHash == null)
{
Console.WriteLine("Could not find ComputeHash!");
return;
}
MethodInfo sign = type.GetMethod("Sign");
if (sign == null)
{
Console.WriteLine("Could not find sign!");
return;
}
Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
Set(type, license, "InstallationId", instalId);
Set(type, license, "Id", Guid.NewGuid());
Set(type, license, "Name", userName);
Set(type, license, "BillingEmail", email);
Set(type, license, "BusinessName", String.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName);
Set(type, license, "Enabled", true);
Set(type, license, "Plan", "Enterprise (Annually)");
Set(type, license, "PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
Set(type, license, "Seats", Int32.MaxValue);
Set(type, license, "MaxCollections", Int16.MaxValue);
Set(type, license, "UsePolicies", true);
Set(type, license, "UseSso", true);
Set(type, license, "UseKeyConnector", true);
Set(type, license, "UseScim", true);
Set(type, license, "UseGroups", true);
Set(type, license, "UseEvents", true);
Set(type, license, "UseDirectory", true);
Set(type, license, "UseTotp", true);
Set(type, license, "Use2fa", true);
Set(type, license, "UseApi", true);
Set(type, license, "UseResetPassword", true);
Set(type, license, "MaxStorageGb", storage == 0 ? Int16.MaxValue : storage);
Set(type, license, "SelfHost", true);
Set(type, license, "UsersGetPremium", true);
Set(type, license, "UseCustomPermissions", true);
Set(type, license, "Version", 16);
Set(type, license, "Issued", DateTime.UtcNow);
Set(type, license, "Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1));
Set(type, license, "Expires", DateTime.UtcNow.AddYears(100));
Set(type, license, "ExpirationWithoutGracePeriod", DateTime.UtcNow.AddYears(100));
Set(type, license, "UsePasswordManager", true);
Set(type, license, "UseSecretsManager", true);
Set(type, license, "SmSeats", Int32.MaxValue);
Set(type, license, "SmServiceAccounts", Int32.MaxValue);
Set(type, license, "UseRiskInsights", true);
Set(type, license, "LimitCollectionCreationDeletion", true);
Set(type, license, "AllowAdminAccessToAllCollectionItems", true);
Set(type, license, "Trial", false);
Set(type, license, "LicenseType", Enum.Parse(licenseTypeEnum, "Organization"));
Set(type, license, "UseOrganizationDomains", true);
Set(type, license, "UseAdminSponsoredFamilies", true);
Set(type, license, "Hash", Convert.ToBase64String((Byte[])computeHash.Invoke(license, [])!));
Set(type, license, "Signature", Convert.ToBase64String((Byte[])sign.Invoke(license, [cert])!));
Console.WriteLine(JsonSerializer.Serialize(license, JsonOptions));
}
private static void Set(Type type, Object license, String name, Object value)
{
type.GetProperty(name)?.SetValue(license, value);
}
} }

View File

@@ -1,11 +0,0 @@
#!/bin/bash
script_dir=`cd $(dirname $0); pwd`
cd $script_dir
dotnet restore
dotnet publish
docker build . -t bitbetter/licensegen # --squash

View File

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

View File

@@ -1,12 +0,0 @@
#!/bin/bash
script_dir=`cd $(dirname $0); pwd`
if [ "$#" -lt "1" ]; then
echo "USAGE: $0 <ABSOLUTE PATH TO CERT.PFX> [License Gen args...]"
exit 1
fi
cert_path=$1
shift
docker run -it -v "$cert_path:/cert.pfx" bitbetter/licensegen "$@"