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
24 changed files with 1119 additions and 513 deletions

View File

@@ -1,15 +1,21 @@
version: 2
version: 2.1
jobs:
build:
docker:
- image: microsoft/dotnet:2-sdk
machine: true
steps:
- checkout
- run:
name: Print the Current Time
command: date
- setup_remote_docker
- run: { name: 'Get docker', command: 'curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh' }
- run:
name: Generate Keys
command: ./generateKeys.sh
- run:
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/
bin/
obj/
src/licenseGen/.vs/*
src/bitBetter/.vs/*
*.dll
*.pem
.vscode/
*.pfx
*.cer
*.vsidx
.DS_Store

View File

@@ -1 +0,0 @@
Need an empty folder

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

191
README.md
View File

@@ -1,61 +1,174 @@
# BitBetter
# BitBetter lite
This project is a tool to modify bitwardens core dll to allow me to self license.
Beware this does janky IL magic to rewrite the bitwarden core dll and install my self signed certificate.
BitBetter is is a tool to modify Bitwarden's core dll to allow you to generate your own individual and organisation licenses.
Yes, there still are quite a few things that need to be fixed. Updates and Organization Buiness Name is hardcoded to Bitbetter, are the first to things to fix.. Better handling of the User-GUID comes to mind too.
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.
## Building
_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!_
To build your own `bitwarden/api` image run
```bash
./build.sh
Credit to https://github.com/h44z/BitBetter and https://github.com/jakeswenson/BitBetter and https://github.com/GieltjE/BitBetter
# Table of Contents
- [BitBetter](#bitbetter)
- [Table of Contents](#table-of-contents)
- [Getting Started](#getting-started)
- [Dependencies](#dependencies)
- [Setting up BitBetter](#setting-up-bitbetter)
- [Optional: Manually generating Certificate & Key](#optional-manually-generating-certificate--key)
- [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
The following instructions are for unix-based systems (Linux, BSD, macOS) and Windows, just choose the correct script extension (.sh or .ps1 respectively).
## Dependencies
Aside from docker, which you also need for Bitwarden, BitBetter requires the following:
* Bitwarden (tested with 2025.11.1 might work on lower versions), for safety always stay up to date
* openssl (probably already installed on most Linux or WSL systems, any version should work, on Windows it will be auto installed using winget)
## Setting up BitBetter
With your dependencies installed, begin the installation of BitBetter by downloading it through Github or using the git command:
```
git clone https://github.com/jakeswenson/BitBetter.git
```
replace anywhere `bitwarden/api` is used with `bitbetter/api` and give it a go. no promises
### Optional: Manually generating Certificate & Key
## Issuing your own licenses
The repo is setup to replace the licesning signing cert in bitwarden.core with your own personal self signed cert (`cert.pfx`)
If you want to be able to sign your own licenses obviously you'll have to replace it with your own self signed cert.
### Signing licesnses
There is a tool included to generate a license (see `src/liceseGen/`)
generate a PFX above using a password of `test` and then build the tool using:
If you wish to generate your self-signed cert & key manually, you can run the following commands.
```bash
./src/licenseGen/build.sh
cd .keys
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cer -days 36500 -outform DER -passout pass:test
openssl x509 -inform DER -in cert.cer -out cert.pem
openssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem -passin pass:test -passout pass:test
```
This tool build ontop of the bitbetter/api container image so make sure you've built that above using the root `./build.sh` script.
> Note that the password here must be `test`.<sup>[1](#f1)</sup>
---
After that you can run the tool using:
```bash
cd ~/BitBetter/src/licenseGen
./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"
## 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:
```
./build.[sh|ps1]
```
# Questions (you might have?)
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`.
I'll work on updates in the next couple weeks, right now, I just wanted something to start with.
Afterwards it will automatically generate the license generator and start all previously specified containers which are **now ready to accept self-issued licenses.**
## But why? Its open source?
Yes, bitwarden is great. If I didn't care about it i wouldn't be doing this.
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
## Updating Bitwarden and BitBetter
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.
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.
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...
## 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!**
## Migrating from mssql to a real database
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
```
Run build.sh and ensure your new instance serves a webpage AND has populated the new database with the tables (should be empty now)
Proceed to stop the new container for now.
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
Export data only from the old sql server database, if needed import the .bak file to a local mssql instance.
Only export tables that have rows, makes it much quicker, .json is the easiest with navicat.
Import the rows to the real database, start the new docker container.
---
# FAQ: Questions you might have.
## Why build a license generator for open source software?
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.
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.

View File

@@ -1,95 +0,0 @@
These are the commands I used (and a few of my notes) for a minimal Debian Stretch install..
Software Selection - "SSH server" and 'standard system utilities" only
sudo apt-get update
sudo apt-get install vim vim-doc vim-scripts wget curl git
sudo apt-get dist-upgrade
sudo reboot
---The next few lines I setup my server authentication and other misc profile settings you can skip until sudo apt-get
ssh-keygen
echo "ssh-rsa AA...1Q== " >> .ssh/authorized_keys
echo ":set mouse=" >> .vimrc
echo "set nocompatible" >> .vimrc
vi .bashrc
sudo visudo ---- tom ALL=(ALL) NOPASSWD:ALL
sudo vi /etc/ssh/sshd_config - Enable 'PermitRootLogin prohibit-password' and 'PasswordAuthentication no'
sudo apt-get update
sudo apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
sudo apt update
apt-cache policy docker-ce
sudo apt install docker-ce
sudo systemctl status docker
sudo usermod -aG docker ${USER}
exit
id -nG
docker version
docker info
docker run hello-world
sudo curl -L --fail https://github.com/docker/compose/releases/download/1.23.1/run.sh -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg
sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
wget -q https://packages.microsoft.com/config/debian/9/prod.list
sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list
curl -s -o bitwarden.sh https://raw.githubusercontent.com/bitwarden/core/master/scripts/bitwarden.sh && sudo chmod u+x bitwarden.sh
./bitwarden.sh install
sudo apt-get update
sudo apt-get install dotnet-sdk-2.1
git clone https://github.com/online-stuff/BitBetter.git
cd BitBetter/src/licenseGen/
dotnet add package Newtonsoft.Json --version 11.0.0
cd ~/BitBetter/src/bitBetter
dotnet add package Newtonsoft.Json --version 11.0.0
cd ~/BitBetter/.keys
rm *
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
cd ~/BitBetter
./build.sh
cd src/licenseGen/
./build.sh
cd ~
vi ~/bwdata/docker/docker-compose.yml - Change image: bitwarden/api:1.26.0 to image: bitbetter/api
vi ~/bwdata/env/global.override.env - Enter mail__smtp relay settings
vi ~/bwdata/scripts/run.sh - function restart() { dockerComposePull to #dockerComposePull
./bitwarden start
----Server should be up and running.. Create a user account
cd ~/BitBetter/src/licenseGen/
./build.sh
./run.sh ~/BitBetter/.keys/cert.pfx user "Name" "EMail" "User-GUID"
Get User-GUID from the admin portal of the BitWarden server
./run.sh ~/BitBetter/.keys/cert.pfx org "Name" "EMail" "Install-ID used to install the server from bitwarden.com/host"

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
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
dotnet publish
# define services to patch
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 ./.keys/cert.cert /newLicensing.cer
COPY . /bitBetter
COPY cert.cer /app/
RUN dotnet /bitBetter/bitBetter.dll && \
echo "modified dll" && \
mv /app/Core.dll /app/Core.orig.dll && \
mv /app/modified.dll /app/Core.dll && \
echo "replaced dll" && \
rm -rf /bitBetter && rm -rf /newLicensing.cer && \
echo "cleaned up"
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/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.Linq;
using System.Security.Cryptography.X509Certificates;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using dnlib.DotNet.Writer;
using dnlib.IO;
namespace bitwardenSelfLicensor
namespace bitBetter;
internal class Program
{
class Program
{
static int Main(string[] args)
{
string cerFile;
string corePath;
private static Int32 Main()
{
const String certFile = "/app/cert.cer";
String[] files = Directory.GetFiles("/app/mount", "Core.dll", SearchOption.AllDirectories);
if(args.Length >= 2) {
cerFile = args[0];
corePath = args[1];
} else if (args.Length == 1) {
cerFile = args[0];
corePath = "/app/Core.dll";
}
else {
cerFile = "/newLicensing.cer";
corePath = "/app/Core.dll";
}
foreach (String file in files)
{
Console.WriteLine(file);
ModuleDefMD moduleDefMd = ModuleDefMD.Load(file);
Byte[] cert = File.ReadAllBytes(certFile);
EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources.OfType<EmbeddedResource>().First(r => r.Name.Equals("Bit.Core.licensing.cer"));
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)));
var cert = File.ReadAllBytes(cerFile);
DataReader reader = embeddedResourceToRemove.CreateReader();
X509Certificate2 existingCert = new(reader.ReadRemainingBytes());
var x = module.Resources.OfType<EmbeddedResource>()
.Where(r => r.Name.Equals("Bit.Core.licensing.cer"))
.First();
Console.WriteLine($"Existing Cert Thumbprint: {existingCert.Thumbprint}");
X509Certificate2 certificate = new(cert);
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);
module.Resources.Remove(x);
Instruction instructionToPatch = constructor.Body.Instructions.FirstOrDefault(i => i.OpCode == OpCodes.Ldstr && String.Equals((String)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase));
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();
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;
}
}
return 0;
}
}

View File

@@ -1,12 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Cecil" Version="0.10.0-beta6" />
<PackageReference Include="dnlib" Version="4.5.0" />
</ItemGroup>
</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.IO;
using System.Linq;
using System.Text.Json;
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.CommandLineUtils;
using Newtonsoft.Json;
using McMaster.Extensions.CommandLineUtils;
namespace bitwardenSelfLicensor
namespace licenseGen;
internal class Program
{
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);
private static readonly CommandLineApplication App = new();
private static readonly CommandOption Cert = App.Option("--cert", "Certificate file", CommandOptionType.SingleValue);
private static readonly CommandOption CoreDll = App.Option("--core", "Path to Core.dll", CommandOptionType.SingleValue);
bool certExists()
{
return File.Exists(cert.Value());
}
private static Int32 Main(String[] args)
{
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()
{
return File.Exists(coreDll.Value());
}
config.OnExecute(() =>
{
Check();
Console.WriteLine("Interactive license mode...");
bool verifyTopOptions()
{
return !string.IsNullOrWhiteSpace(cert.Value()) &&
!string.IsNullOrWhiteSpace(coreDll.Value()) &&
certExists() && coreExists();
}
while (licenseType == "")
{
Console.WriteLine("What would you like to generate, a [u]ser license or an [o]rg license: ");
buff = Console.ReadLine();
app.Command("user", config =>
{
var name = config.Argument("Name", "your name");
var email = config.Argument("Email", "your email");
var userIdArg = config.Argument("User ID", "your user id");
var key = config.Argument("Key", "your key id (optional)");
var help = config.HelpOption("--help | -h | -?");
switch (buff)
{
case "u":
{
licenseType = "user";
Console.WriteLine("Okay, we will generate a user license.");
config.OnExecute(() =>
{
if (!verifyTopOptions())
{
if (!coreExists())
{
config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}");
}
if (!certExists())
{
config.Error.WriteLine($"Cant find certificate at: {cert.Value()}");
}
while (!validGuid)
{
Console.WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]: ");
buff = Console.ReadLine();
config.ShowHelp();
return 1;
}
else if (string.IsNullOrWhiteSpace(name.Value) || string.IsNullOrWhiteSpace(email.Value))
{
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}'");
config.ShowHelp("user");
return 1;
}
if (Guid.TryParse(buff, out guid))validGuid = true;
else Console.WriteLine("The user-guid provided does not appear to be valid!");
}
break;
}
case "o":
{
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))
{
config.Error.WriteLine($"User ID not provided");
config.ShowHelp("user");
return 1;
}
if (Guid.TryParse(buff, out installid)) validInstallid = true;
else Console.WriteLine("The install-id provided does not appear to be valid.");
}
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;
});
});
app.Command("org", config =>
{
var name = config.Argument("Name", "your name");
var email = config.Argument("Email", "your email");
var installId = config.Argument("InstallId", "your installation id (GUID)");
var key = config.Argument("Key", "your key id (optional)");
var help = config.HelpOption("--help | -h | -?");
while (name == "")
{
Console.WriteLine("Please provide the username this license will be registered to. [username]: ");
buff = Console.ReadLine();
if (CheckUsername(buff)) name = buff;
}
config.OnExecute(() =>
{
if (!verifyTopOptions())
{
if (!coreExists())
{
config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}");
}
if (!certExists())
{
config.Error.WriteLine($"Cant find certificate at: {cert.Value()}");
}
while (email == "")
{
Console.WriteLine("Please provide the email address for the user " + name + ". [email]: ");
buff = Console.ReadLine();
if (CheckEmail(buff))
{
email = buff;
}
}
config.ShowHelp();
return 1;
}
else if (string.IsNullOrWhiteSpace(name.Value) ||
string.IsNullOrWhiteSpace(email.Value) ||
string.IsNullOrWhiteSpace(installId.Value))
{
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}' InstallId='{installId.Value}'");
config.ShowHelp("org");
return 1;
}
while (storage == 0)
{
Console.WriteLine("Extra storage space for the user " + name + ". (max.: " + Int16.MaxValue + "). Defaults to maximum value. [storage]");
buff = Console.ReadLine();
if (String.IsNullOrWhiteSpace(buff))
{
storage = Int16.MaxValue;
}
else
{
if (CheckStorage(buff))
{
storage = Int16.Parse(buff);
}
}
}
if (!Guid.TryParse(installId.Value, out Guid installationId))
{
config.Error.WriteLine("Unable to parse your installation id as a GUID");
config.Error.WriteLine($"Here's a new guid: {Guid.NewGuid()}");
config.ShowHelp("org");
return 1;
}
switch (licenseType)
{
case "user":
{
Console.WriteLine("Confirm creation of \"user\" license for username: \"" + name + "\", email: \"" + email + "\", Storage: \"" + storage + " GB\", User-GUID: \"" + guid + "\"? Y/n");
buff = Console.ReadLine();
if (buff is "" or "y" or "Y")
{
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(() =>
{
app.ShowHelp();
return 10;
});
return 0;
});
});
App.Command("user", config =>
{
CommandArgument name = config.Argument("Name", "your name");
CommandArgument email = config.Argument("Email", "your email");
CommandArgument userIdArg = config.Argument("User ID", "your user id");
CommandArgument storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + Int16.MaxValue + " (optional, default = max)");
CommandArgument key = config.Argument("Key", "your key id (optional)");
app.HelpOption("-? | -h | --help");
config.OnExecute(() =>
{
Check();
try
{
return app.Execute(args);
}
catch (Exception e)
{
Console.Error.WriteLine("Oops: {0}", e);
return 100;
}
}
if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value))
{
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}'");
config.ShowHelp(true);
return 1;
}
static void GenerateUserLicense(X509Certificate2 cert, string corePath,
string userName, string email, Guid userId, string key)
{
var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath);
if (String.IsNullOrWhiteSpace(userIdArg.Value) || !Guid.TryParse(userIdArg.Value, out Guid userId))
{
config.Error.WriteLine("User ID not provided");
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)
{
type.GetProperty(name).SetValue(license, value);
}
return 0;
});
});
App.Command("org", config =>
{
CommandArgument name = config.Argument("Name", "your name");
CommandArgument email = config.Argument("Email", "your email");
CommandArgument installId = config.Argument("InstallId", "your installation id (GUID)");
CommandArgument storage = config.Argument("Storage", "extra storage space in GB. Maximum is " + Int16.MaxValue + " (optional, default = max)");
CommandArgument businessName = config.Argument("BusinessName", "name for the organization (optional)");
CommandArgument key = config.Argument("Key", "your key id (optional)");
set("LicenseKey", string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
set("Id", userId);
set("Name", userName);
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);
config.OnExecute(() =>
{
Check();
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 })));
if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value) || String.IsNullOrWhiteSpace(installId.Value))
{
config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}' InstallId='{installId.Value}'");
config.ShowHelp(true);
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,
string userName, string email, Guid instalId, string key)
{
var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath);
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 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)
{
type.GetProperty(name).SetValue(license, value);
}
App.OnExecute(() =>
{
App.ShowHelp();
return 10;
});
set("LicenseKey", string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key);
set("InstallationId", instalId);
set("Id", Guid.NewGuid());
set("Name", userName);
set("BillingEmail", email);
set("BusinessName", "BitBetter");
set("Enabled", true);
set("Plan", "Custom");
set("PlanType", (byte)6);
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);
try
{
App.HelpOption("-? | -h | --help");
return App.Execute(args);
}
catch (Exception exception)
{
Console.Error.WriteLine("Oops: {0}", exception);
return 100;
}
}
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 })));
private static void Check()
{
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">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
</ItemGroup>
</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 "$@"