diff --git a/.circleci/config.yml b/.circleci/config.yml index acb6ba1..443f66e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,10 +12,10 @@ jobs: command: ./generateKeys.sh - run: name: Build script - command: ./build.sh y + command: ./build.sh update - run: name: Test generating user license command: ./licenseGen.sh user TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23 - run: name: Test generating organization license - command: ./licenseGen.sh org TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23 + command: ./licenseGen.sh org TestName TestEmail@example.com 4a619d4a-522d-4c70-8596-affb5b607c23 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 249b5ef..56566e8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,5 @@ src/bitBetter/.vs/* *.pem .vscode/ *.pfx -*.cert +*.cer *.vsidx diff --git a/.servers/serverlist.txt b/.servers/serverlist.txt index f1debc9..3d99d6b 100644 --- a/.servers/serverlist.txt +++ b/.servers/serverlist.txt @@ -1,4 +1,4 @@ # Uncomment a line below and fill in the missing values or add your own. Every line in this file will be called by build.[sh|ps1] once the patched image is built. -# docker run -d --name bitwarden -v \logs:/var/log/bitwarden -v \bwdata:/etc/bitwarden -p 80:8080 --env-file \settings.env bitwarden-patch +# docker run -d --name bitwarden -v \logs:/var/log/bitwarden -v \bwdata:/etc/bitwarden -p 80:8080 --env-file \settings.env bitwarden-patched # # docker-compose -f /docker-compose.yml up -d diff --git a/README.md b/README.md index 6890eee..78a56f6 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,8 @@ If you wish to generate your self-signed cert & key manually, you can run the fo ```bash cd .keys -openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cert -days 36500 -outform DER -passout pass:test -openssl x509 -inform DER -in cert.cert -out cert.pem +openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cer -days 36500 -outform DER -passout pass:test +openssl x509 -inform DER -in cert.cer -out cert.pem openssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem -passin pass:test -passout pass:test ``` @@ -70,7 +70,7 @@ From the BitBetter directory, simply run: ./build.[sh|ps1] ``` -This will create a new self-signed certificate in the `.keys` directory if one does not already exist and then create a modified version of the official `ghcr.io/bitwarden/self-host` image called `bitwarden-patch`. +This will create a new self-signed certificate in the `.keys` directory if one does not already exist and then create a modified version of the official `ghcr.io/bitwarden/self-host` image called `bitwarden-patched`. Afterwards it will automatically generate the license generator and start all previously specified containers which are **now ready to accept self-issued licenses.** diff --git a/build.ps1 b/build.ps1 index 6576d81..87aa351 100644 --- a/build.ps1 +++ b/build.ps1 @@ -7,7 +7,7 @@ $tempdirectory = "$pwd\temp" $components = "Api","Identity" # delete old directories / files if applicable -if (Test-Path "$tempdirectory") { +if (Test-Path "$tempdirectory" -PathType Container) { Remove-Item "$tempdirectory" -Recurse -Force } @@ -19,24 +19,28 @@ if (Test-Path -Path "$pwd\src\licenseGen\cert.pfx" -PathType Leaf) { Remove-Item "$pwd\src\licenseGen\cert.pfx" -Force } -if (Test-Path -Path "$pwd\src\bitBetter\cert.cert" -PathType Leaf) { - Remove-Item "$pwd\src\bitBetter\cert.cert" -Force +if (Test-Path -Path "$pwd\src\bitBetter\cert.cer" -PathType Leaf) { + Remove-Item "$pwd\src\bitBetter\cert.cer" -Force } +if (Test-Path "$pwd\.keys\cert.cert" -PathType Leaf) { + Rename-Item -Path "$pwd\.keys\cert.cert" -NewName "$pwd\.keys\cert.cer" +} + + # generate keys if none are available -if (!(Test-Path "$pwd\.keys")) { +if (!(Test-Path "$pwd\.keys" -PathType Container)) { .\generateKeys.ps1 } -# copy the key to bitBetter and licenseGen -Copy-Item "$pwd\.keys\cert.cert" -Destination "$pwd\src\bitBetter" -Copy-Item "$pwd\.keys\cert.pfx" -Destination "$pwd\src\licenseGen" +# copy the key to bitBetter +Copy-Item "$pwd\.keys\cert.cer" -Destination "$pwd\src\bitBetter" # build bitBetter and clean the source directory after docker build --no-cache -t bitbetter/bitbetter "$pwd\src\bitBetter" -Remove-Item "$pwd\src\bitBetter\cert.cert" -Force +Remove-Item "$pwd\src\bitBetter\cert.cer" -Force -# gather all running instances +# gather all running instances, cannot run a wildcard filter on Ancestor= :( $oldinstances = docker container ps --all -f Name=bitwarden --format '{{.ID}}' # stop and remove all running instances @@ -46,25 +50,35 @@ foreach ($instance in $oldinstances) { } # update bitwarden itself -if ($args[0] -eq 'y') -{ +if ($args[0] -eq 'update') { docker pull ghcr.io/bitwarden/self-host:beta -} -else -{ - $confirmation = Read-Host "Update (or get) bitwarden source container" +} else { + $confirmation = Read-Host "Update (or get) bitwarden source container (y/n)" if ($confirmation -eq 'y') { docker pull ghcr.io/bitwarden/self-host:beta } } -# remove previous existing patch(ed) image -if (docker image ls -q bitwarden-patch) { - docker image rm bitwarden-patch +# stop and remove previous existing patch(ed) container +$oldinstances = docker container ps --all -f Ancestor=bitwarden-patched --format '{{.ID}}' +foreach ($instance in $oldinstances) { + docker stop $instance + docker rm $instance +} +$oldinstances = docker image ls bitwarden-patched --format '{{.ID}}' +foreach ($instance in $oldinstances) { + docker image rm $instance +} + +# remove old extract containers +$oldinstances = docker container ps --all -f Name=bitwarden-extract --format '{{.ID}}' +foreach ($instance in $oldinstances) { + docker stop $instance + docker rm $instance } # start a new bitwarden instance so we can patch it -$patchinstance = docker run -d --name bitwarden-patch ghcr.io/bitwarden/self-host:beta +$patchinstance = docker run -d --name bitwarden-extract ghcr.io/bitwarden/self-host:beta # create our temporary directory New-item -ItemType Directory -Path $tempdirectory @@ -75,33 +89,38 @@ foreach ($component in $components) { docker cp $patchinstance`:/app/$component/Core.dll "$tempdirectory\$component\Core.dll" } +# stop and remove our temporary container +docker stop bitwarden-extract +docker rm bitwarden-extract + # run bitBetter, this applies our patches to the required files docker run -v "$tempdirectory`:/app/mount" --rm bitbetter/bitbetter # create a new image with the patched files -docker build . --tag bitwarden-patch --file "$pwd\src\bitBetter\Dockerfile-bitwarden-patch" - -# stop and remove our temporary container -docker stop bitwarden-patch -docker rm bitwarden-patch - -# copy our patched library to the licenseGen source directory -Copy-Item "$tempdirectory\Identity\Core.dll" -Destination "$pwd\src\licenseGen" - -# remove our temporary directory -Remove-Item "$tempdirectory" -Recurse -Force +docker build . --tag bitwarden-patched --file "$pwd\src\bitBetter\Dockerfile-bitwarden-patch" # start all user requested instances -foreach($line in Get-Content "$pwd\.servers\serverlist.txt") { - Invoke-Expression "& $line" +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 \ No newline at end of file diff --git a/build.sh b/build.sh index f0417b1..3f43b92 100755 --- a/build.sh +++ b/build.sh @@ -20,8 +20,12 @@ if [ -f "$PWD/src/licenseGen/cert.pfx" ]; then rm -f "$PWD/src/licenseGen/cert.pfx" fi -if [ -f "$PWD/src/bitBetter/cert.cert" ]; then - rm -f "$PWD/src/bitBetter/cert.cert" +if [ -f "$PWD/src/bitBetter/cert.cer" ]; then + rm -f "$PWD/src/bitBetter/cert.cer" +fi + +if [ -f "$PWD/.keys/cert.cert" ]; then + mv "$PWD/.keys/cert.cert" "$PWD/.keys/cert.cer" fi # generate keys if none are available @@ -29,15 +33,14 @@ if [ ! -d "$PWD/.keys" ]; then ./generateKeys.sh fi -# copy the key to bitBetter and licenseGen -cp -f "$PWD/.keys/cert.cert" "$PWD/src/bitBetter" -cp -f "$PWD/.keys/cert.pfx" "$PWD/src/licenseGen" +# copy the key to bitBetter +cp -f "$PWD/.keys/cert.cer" "$PWD/src/bitBetter" # build bitBetter and clean the source directory after docker build --no-cache -t bitbetter/bitbetter "$PWD/src/bitBetter" -rm -f "$PWD/src/bitBetter/cert.cert" +rm -f "$PWD/src/bitBetter/cert.cer" -# gather all running instances +# gather all running instances, cannot run a wildcard filter on Ancestor= :( OLDINSTANCES=$(docker container ps --all -f Name=bitwarden --format '{{.ID}}') # stop and remove all running instances @@ -47,10 +50,10 @@ for INSTANCE in ${OLDINSTANCES[@]}; do done # update bitwarden itself -if [ "$1" = "y" ]; then +if [ "$1" = "update" ]; then docker pull ghcr.io/bitwarden/self-host:beta else - read -p "Update (or get) bitwarden source container: " -n 1 -r + read -p "Update (or get) bitwarden source container (y/n): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]] then @@ -58,13 +61,26 @@ else fi fi -# remove previous existing patch(ed) image -if [ "$(docker image ls -q bitwarden-patch)" ]; then - docker image rm bitwarden-patch -fi +# stop and remove previous existing patch(ed) container +OLDINSTANCES=$(docker container ps --all -f Ancestor=bitwarden-patched --format '{{.ID}}') +for INSTANCE in ${OLDINSTANCES[@]}; do + docker stop $INSTANCE + docker rm $INSTANCE +done +OLDINSTANCES=$(docker image ls bitwarden-patched --format '{{.ID}}') +for INSTANCE in ${OLDINSTANCES[@]}; do + docker image rm $INSTANCE +done + +# remove old extract containers +OLDINSTANCES=$(docker container ps --all -f Name=bitwarden-extract --format '{{.ID}}') +for INSTANCE in ${OLDINSTANCES[@]}; do + docker stop $INSTANCE + docker rm $INSTANCE +done # start a new bitwarden instance so we can patch it -PATCHINSTANCE=$(docker run -d --name bitwarden-patch ghcr.io/bitwarden/self-host:beta) +PATCHINSTANCE=$(docker run -d --name bitwarden-extract ghcr.io/bitwarden/self-host:beta) # create our temporary directory mkdir $TEMPDIRECTORY @@ -75,34 +91,39 @@ for COMPONENT in ${COMPONENTS[@]}; do docker cp $PATCHINSTANCE:/app/$COMPONENT/Core.dll "$TEMPDIRECTORY/$COMPONENT/Core.dll" done +# stop and remove our temporary container +docker stop bitwarden-extract +docker rm bitwarden-extract + # run bitBetter, this applies our patches to the required files docker run -v "$TEMPDIRECTORY:/app/mount" --rm bitbetter/bitbetter # create a new image with the patched files -docker build . --tag bitwarden-patch --file "$PWD/src/bitBetter/Dockerfile-bitwarden-patch" - -# stop and remove our temporary container -docker stop bitwarden-patch -docker rm bitwarden-patch - -# copy our patched library to the licenseGen source directory -cp -f "$TEMPDIRECTORY/Identity/Core.dll" "$PWD/src/licenseGen" - -# remove our temporary directory -rm -rf "$TEMPDIRECTORY" +docker build . --tag bitwarden-patched --file "$PWD/src/bitBetter/Dockerfile-bitwarden-patch" # start all user requested instances -sed -i 's/\r$//' "$PWD/.servers/serverlist.txt" -cat "$PWD/.servers/serverlist.txt" | while read -r LINE; do - bash -c "$LINE" -done +if [ -f "$PWD/src/bitBetter/cert.cer" ]; then + sed -i 's/\r$//' "$PWD/.servers/serverlist.txt" + cat "$PWD/.servers/serverlist.txt" | while read -r LINE; do + if [[ $LINE == "#*" ]]; then + bash -c "$LINE" + 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" \ No newline at end of file diff --git a/generateKeys.ps1 b/generateKeys.ps1 index d20aa14..1a82714 100644 --- a/generateKeys.ps1 +++ b/generateKeys.ps1 @@ -20,6 +20,6 @@ if (Test-Path "$pwd\.keys") New-item -ItemType Directory -Path "$pwd\.keys" # generate actual keys -Invoke-Expression "& '$opensslbinary' req -x509 -newkey rsa:4096 -keyout `"$pwd\.keys\key.pem`" -out `"$pwd\.keys\cert.cert`" -days 36500 -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -outform DER -passout pass:test" -Invoke-Expression "& '$opensslbinary' x509 -inform DER -in `"$pwd\.keys\cert.cert`" -out `"$pwd\.keys\cert.pem`"" +Invoke-Expression "& '$opensslbinary' req -x509 -newkey rsa:4096 -keyout `"$pwd\.keys\key.pem`" -out `"$pwd\.keys\cert.cer`" -days 36500 -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -outform DER -passout pass:test" +Invoke-Expression "& '$opensslbinary' x509 -inform DER -in `"$pwd\.keys\cert.cer`" -out `"$pwd\.keys\cert.pem`"" Invoke-Expression "& '$opensslbinary' pkcs12 -export -out `"$pwd\.keys\cert.pfx`" -inkey `"$pwd\.keys\key.pem`" -in `"$pwd\.keys\cert.pem`" -passin pass:test -passout pass:test" \ No newline at end of file diff --git a/generateKeys.sh b/generateKeys.sh index d561aee..a700ce6 100755 --- a/generateKeys.sh +++ b/generateKeys.sh @@ -15,6 +15,6 @@ fi mkdir "$DIR" # Generate new keys -openssl req -x509 -newkey rsa:4096 -keyout "$DIR/key.pem" -out "$DIR/cert.cert" -days 36500 -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -outform DER -passout pass:test -openssl x509 -inform DER -in "$DIR/cert.cert" -out "$DIR/cert.pem" +openssl req -x509 -newkey rsa:4096 -keyout "$DIR/key.pem" -out "$DIR/cert.cer" -days 36500 -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -outform DER -passout pass:test +openssl x509 -inform DER -in "$DIR/cert.cer" -out "$DIR/cert.pem" openssl pkcs12 -export -out "$DIR/cert.pfx" -inkey "$DIR/key.pem" -in "$DIR/cert.pem" -passin pass:test -passout pass:test diff --git a/licenseGen.ps1 b/licenseGen.ps1 index 36815d6..7b69b43 100644 --- a/licenseGen.ps1 +++ b/licenseGen.ps1 @@ -10,7 +10,7 @@ if ($($args.Count) -lt 1) { Exit 1 } -if ($args[0] = "interactive") { +if ($args[0] -eq "interactive") { docker run -it --rm bitbetter/licensegen interactive } else { docker run bitbetter/licensegen $args diff --git a/src/bitBetter/Dockerfile b/src/bitBetter/Dockerfile index 91a7f8e..e93b428 100644 --- a/src/bitBetter/Dockerfile +++ b/src/bitBetter/Dockerfile @@ -2,7 +2,7 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /bitBetter COPY . /bitBetter -COPY cert.cert /app/ +COPY cert.cer /app/ RUN dotnet restore RUN dotnet publish -c Release -o /app --no-restore @@ -11,4 +11,4 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 WORKDIR /app COPY --from=build /app . -ENTRYPOINT [ "/app/bitBetter" ] \ No newline at end of file +ENTRYPOINT ["dotnet", "/app/bitBetter.dll"] \ No newline at end of file diff --git a/src/bitBetter/Dockerfile-bitwarden-patch b/src/bitBetter/Dockerfile-bitwarden-patch index c99e2df..c14b2b3 100644 --- a/src/bitBetter/Dockerfile-bitwarden-patch +++ b/src/bitBetter/Dockerfile-bitwarden-patch @@ -1,4 +1,3 @@ FROM ghcr.io/bitwarden/self-host:beta -COPY ./temp/Api/Core.dll /app/Api/Core.dll -COPY ./temp/Identity/Core.dll /app/Identity/Core.dll +COPY ./temp/ /app/ \ No newline at end of file diff --git a/src/bitBetter/Program.cs b/src/bitBetter/Program.cs index c6c067e..dec4970 100644 --- a/src/bitBetter/Program.cs +++ b/src/bitBetter/Program.cs @@ -14,7 +14,7 @@ internal class Program { private static Int32 Main() { - const String certFile = "/app/cert.cert"; + const String certFile = "/app/cert.cer"; String[] files = Directory.GetFiles("/app/mount", "Core.dll", SearchOption.AllDirectories); foreach (String file in files) @@ -23,12 +23,7 @@ internal class Program ModuleDefMD moduleDefMd = ModuleDefMD.Load(file); Byte[] cert = File.ReadAllBytes(certFile); - EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources - .OfType() - .First(r => r.Name.Equals("Bit.Core.licensing.cer")); - - Console.WriteLine(embeddedResourceToRemove.Name); - + EmbeddedResource embeddedResourceToRemove = moduleDefMd.Resources.OfType().First(r => r.Name.Equals("Bit.Core.licensing.cer")); EmbeddedResource embeddedResourceToAdd = new("Bit.Core.licensing.cer", cert) { Attributes = embeddedResourceToRemove.Attributes }; moduleDefMd.Resources.Add(embeddedResourceToAdd); moduleDefMd.Resources.Remove(embeddedResourceToRemove); @@ -45,10 +40,7 @@ internal class Program TypeDef type = services.First(t => t.Name == "LicensingService"); MethodDef constructor = type.FindConstructors().First(); - Instruction instructionToPatch = - constructor.Body.Instructions - .FirstOrDefault(i => i.OpCode == OpCodes.Ldstr - && String.Equals((String)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase)); + Instruction instructionToPatch = constructor.Body.Instructions.FirstOrDefault(i => i.OpCode == OpCodes.Ldstr && String.Equals((String)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase)); if (instructionToPatch != null) { diff --git a/src/bitBetter/bitBetter.csproj b/src/bitBetter/bitBetter.csproj index d996f47..6433b92 100644 --- a/src/bitBetter/bitBetter.csproj +++ b/src/bitBetter/bitBetter.csproj @@ -1,12 +1,9 @@ - Exe net8.0 - - + - diff --git a/src/licenseGen/Dockerfile b/src/licenseGen/Dockerfile index 2b5d261..1c8eb9b 100644 --- a/src/licenseGen/Dockerfile +++ b/src/licenseGen/Dockerfile @@ -12,4 +12,4 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 WORKDIR /app COPY --from=build /app . -ENTRYPOINT [ "dotnet", "/app/licenseGen.dll", "--core", "/app/Core.dll", "--cert", "/app/cert.pfx" ] \ No newline at end of file +ENTRYPOINT ["dotnet", "/app/licenseGen.dll", "--cert=/app/cert.pfx", "--core=/app/Core.dll"] diff --git a/src/licenseGen/Program.cs b/src/licenseGen/Program.cs index e66b61d..6c9c327 100644 --- a/src/licenseGen/Program.cs +++ b/src/licenseGen/Program.cs @@ -1,57 +1,31 @@ using System; using System.IO; +using System.Text.Json; using System.Reflection; using System.Runtime.Loader; using System.Security.Cryptography.X509Certificates; using McMaster.Extensions.CommandLineUtils; -using Newtonsoft.Json; namespace licenseGen; internal class Program { + private static readonly CommandLineApplication App = new(); + private static readonly CommandOption Cert = App.Option("--cert", "Certifcate file", CommandOptionType.SingleValue); + private static readonly CommandOption CoreDll = App.Option("--core", "Path to Core.dll", CommandOptionType.SingleValue); + private static Int32 Main(String[] args) { - CommandLineApplication app = new(); - CommandOption cert = app.Option("--cert", "cert file", CommandOptionType.SingleValue); - CommandOption coreDll = app.Option("--core", "path to core dll", CommandOptionType.SingleValue); - - Boolean CertExists() - { - return File.Exists(cert.Value()); - } - - Boolean CoreExists() - { - return File.Exists(coreDll.Value()); - } - - Boolean VerifyTopOptions() - { - return !String.IsNullOrWhiteSpace(cert.Value()) && - !String.IsNullOrWhiteSpace(coreDll.Value()) && - CertExists() && CoreExists(); - } - - app.Command("interactive", config => + App.Command("interactive", config => { String buff, licenseType = "", name = "", email = "", businessName=""; Int16 storage = 0; - Boolean validGuid = false, validInstallid = false; - Guid guid = new(), installid = new(); + Guid guid = Guid.Empty, installid = Guid.Empty; config.OnExecute(() => { - if (!VerifyTopOptions()) - { - if (!CoreExists()) config.Error.WriteLine($"Can't find core dll at: {coreDll.Value()}"); - if (!CertExists()) config.Error.WriteLine($"Can't find certificate at: {cert.Value()}"); - - config.ShowHelp(); - return 1; - } - + Check(); Console.WriteLine("Interactive license mode..."); while (licenseType == "") @@ -59,51 +33,55 @@ internal class Program Console.WriteLine("What would you like to generate, a [u]ser license or an [o]rg license: "); buff = Console.ReadLine(); - if(buff == "u") + switch (buff) { - licenseType = "user"; - Console.WriteLine("Okay, we will generate a user license."); - - while (validGuid == false) + case "u": { - Console.WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]: "); - buff = Console.ReadLine(); + licenseType = "user"; + Console.WriteLine("Okay, we will generate a user license."); - if (Guid.TryParse(buff, out guid))validGuid = true; - else Console.WriteLine("The user-guid provided does not appear to be valid!"); - } - } - else if (buff == "o") - { - licenseType = "org"; - Console.WriteLine("Okay, we will generate an organization license."); - - while (validInstallid == false) - { - 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 (Guid.TryParse(buff, out installid)) validInstallid = true; - else Console.WriteLine("The install-id provided does not appear to be valid."); - } - - while (businessName == "") - { - Console.WriteLine("Please enter a business name, default is BitBetter. [Business Name]: "); - buff = Console.ReadLine(); - if (buff == "") + while (!validGuid) { - businessName = "BitBetter"; - } - else if (CheckBusinessName(buff)) - { - businessName = buff; + Console.WriteLine("Please provide the user's guid — refer to the Readme for details on how to retrieve this. [GUID]: "); + buff = Console.ReadLine(); + + if (Guid.TryParse(buff, out guid))validGuid = true; + else Console.WriteLine("The user-guid provided does not appear to be valid!"); } + break; } - } - else - { - Console.WriteLine("Unrecognized option \'" + buff + "\'."); + 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 (Guid.TryParse(buff, out installid)) validInstallid = true; + else Console.WriteLine("The install-id provided does not appear to be valid."); + } + + 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; } } @@ -111,7 +89,7 @@ internal class Program { Console.WriteLine("Please provide the username this license will be registered to. [username]: "); buff = Console.ReadLine(); - if ( CheckUsername(buff) ) name = buff; + if (CheckUsername(buff)) name = buff; } while (email == "") @@ -141,39 +119,46 @@ internal class Program } } - if (licenseType == "user") + switch (licenseType) { - 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" ) + case "user": { - GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name, email, storage, guid, null); + 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; + } + + break; } - else + case "org": { - Console.WriteLine("Exiting..."); - return 0; - } - } - else if (licenseType == "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; + 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; + } + + break; } } return 0; }); }); - app.Command("user", config => + App.Command("user", config => { CommandArgument name = config.Argument("Name", "your name"); CommandArgument email = config.Argument("Email", "your email"); @@ -183,20 +168,7 @@ internal class Program config.OnExecute(() => { - if (!VerifyTopOptions()) - { - if (!CoreExists()) - { - config.Error.WriteLine($"Can't find core dll at: {coreDll.Value()}"); - } - if (!CertExists()) - { - config.Error.WriteLine($"Can't find certificate at: {cert.Value()}"); - } - - config.ShowHelp(); - return 1; - } + Check(); if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value)) { @@ -225,12 +197,12 @@ internal class Program storageShort = (Int16) parsedStorage; } - GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value); + GenerateUserLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, userId, key.Value); return 0; }); }); - app.Command("org", config => + App.Command("org", config => { CommandArgument name = config.Argument("Name", "your name"); CommandArgument email = config.Argument("Email", "your email"); @@ -241,24 +213,9 @@ internal class Program config.OnExecute(() => { - if (!VerifyTopOptions()) - { - if (!CoreExists()) - { - config.Error.WriteLine($"Can't find core dll at: {coreDll.Value()}"); - } - if (!CertExists()) - { - config.Error.WriteLine($"Can't find certificate at: {cert.Value()}"); - } + Check(); - config.ShowHelp(); - return 1; - } - - if (String.IsNullOrWhiteSpace(name.Value) || - String.IsNullOrWhiteSpace(email.Value) || - String.IsNullOrWhiteSpace(installId.Value)) + if (String.IsNullOrWhiteSpace(name.Value) || String.IsNullOrWhiteSpace(email.Value) || String.IsNullOrWhiteSpace(installId.Value)) { config.Error.WriteLine($"Some arguments are missing: Name='{name.Value}' Email='{email.Value}' InstallId='{installId.Value}'"); config.ShowHelp(true); @@ -283,34 +240,54 @@ internal class Program config.ShowHelp(true); return 1; } - storageShort = (Int16) parsedStorage; + storageShort = (Int16)parsedStorage; } - GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value); + GenerateOrgLicense(new X509Certificate2(Cert.Value()!, "test"), CoreDll.Value(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value); return 0; }); }); - app.OnExecute(() => + App.OnExecute(() => { - app.ShowHelp(); + App.ShowHelp(); return 10; }); - app.HelpOption("-? | -h | --help"); - try { - return app.Execute(args); + App.HelpOption("-? | -h | --help"); + return App.Execute(args); } - catch (Exception e) + catch (Exception exception) { - Console.Error.WriteLine("Oops: {0}", e); + Console.Error.WriteLine("Oops: {0}", exception); return 100; } } + private static void Check() + { + if (!File.Exists(Cert.Value())) + { + App.Error.WriteLine($"Can't find certificate at: {Cert.Value()}"); + App.ShowHelp(); + Environment.Exit(1); + } + if (!File.Exists(CoreDll.Value())) + { + App.Error.WriteLine($"Can't find core dll at: {CoreDll.Value()}"); + App.ShowHelp(); + Environment.Exit(1); + } + if (Cert == null || String.IsNullOrWhiteSpace(Cert.Value()) || CoreDll == null || String.IsNullOrWhiteSpace(CoreDll.Value())) + { + App.ShowHelp(); + Environment.Exit(1); + } + } + // checkUsername Checks that the username is a valid username private static Boolean CheckUsername(String s) { @@ -356,99 +333,146 @@ internal class Program return false; } + private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; private static void GenerateUserLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid userId, String key) { - Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); + Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath)); Type type = core.GetType("Bit.Core.Billing.Models.Business.UserLicense"); Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); + if (type == null) + { + Console.WriteLine("Could not find type!"); + return; + } + if (licenseTypeEnum == null) + { + Console.WriteLine("Could not find license licenseTypeEnum!"); + return; + } + Object license = Activator.CreateInstance(type); - Set("LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); - Set("Id", userId); - Set("Name", userName); - Set("Email", email); - Set("Premium", true); - Set("MaxStorageGb", storage == 0 ? Int16.MaxValue : storage); - Set("Version", 1); - Set("Issued", DateTime.UtcNow); - Set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1)); - Set("Expires", DateTime.UtcNow.AddYears(100)); - Set("Trial", false); - Set("LicenseType", Enum.Parse(licenseTypeEnum, "User")); - Set("Hash", Convert.ToBase64String((Byte[])type.GetMethod("ComputeHash").Invoke(license, []))); - Set("Signature", Convert.ToBase64String((Byte[])type.GetMethod("Sign").Invoke(license, [cert]))); - - Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); - return; - - void Set(String name, Object value) + MethodInfo computeHash = type.GetMethod("ComputeHash"); + if (computeHash == null) { - type.GetProperty(name)?.SetValue(license, value); + Console.WriteLine("Could not find ComputeHash!"); + return; } - } + MethodInfo sign = type.GetMethod("Sign"); + if (sign == null) + { + Console.WriteLine("Could not find sign!"); + return; + } + + Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); + Set(type, license, "Id", userId); + Set(type, license, "Name", userName); + Set(type, license, "Email", email); + Set(type, license, "Premium", true); + Set(type, license, "MaxStorageGb", storage == 0 ? Int16.MaxValue : storage); + Set(type, license, "Version", 1); + Set(type, license, "Issued", DateTime.UtcNow); + Set(type, license, "Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1)); + Set(type, license, "Expires", DateTime.UtcNow.AddYears(100)); + Set(type, license, "Trial", false); + Set(type, license, "LicenseType", Enum.Parse(licenseTypeEnum, "User")); + Set(type, license, "Hash", Convert.ToBase64String(((Byte[])computeHash.Invoke(license, []))!)); + Set(type, license, "Signature", Convert.ToBase64String((Byte[])sign.Invoke(license, [cert])!)); + + Console.WriteLine(JsonSerializer.Serialize(license, JsonOptions)); + } private static void GenerateOrgLicense(X509Certificate2 cert, String corePath, String userName, String email, Int16 storage, Guid instalId, String businessName, String key) { - Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); - + Assembly core = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(corePath)); Type type = core.GetType("Bit.Core.Billing.Organizations.Models.OrganizationLicense"); Type licenseTypeEnum = core.GetType("Bit.Core.Enums.LicenseType"); Type planTypeEnum = core.GetType("Bit.Core.Billing.Enums.PlanType"); + if (type == null) + { + Console.WriteLine("Could not find type!"); + return; + } + if (licenseTypeEnum == null) + { + Console.WriteLine("Could not find licenseTypeEnum!"); + return; + } + if (planTypeEnum == null) + { + Console.WriteLine("Could not find planTypeEnum!"); + return; + } + Object license = Activator.CreateInstance(type); - Set("LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); - Set("InstallationId", instalId); - Set("Id", Guid.NewGuid()); - Set("Name", userName); - Set("BillingEmail", email); - Set("BusinessName", String.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName); - Set("Enabled", true); - Set("Plan", "Enterprise (Annually)"); - Set("PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually")); - Set("Seats", Int32.MaxValue); - Set("MaxCollections", Int16.MaxValue); - Set("UsePolicies", true); - Set("UseSso", true); - Set("UseKeyConnector", true); - Set("UseScim", true); - Set("UseGroups", true); - Set("UseEvents", true); - Set("UseDirectory", true); - Set("UseTotp", true); - Set("Use2fa", true); - Set("UseApi", true); - Set("UseResetPassword", true); - Set("UseCustomPermissions", true); - Set("MaxStorageGb", storage == 0 ? Int16.MaxValue : storage); - Set("SelfHost", true); - Set("UsersGetPremium", true); - Set("UsePasswordManager", true); - Set("UseSecretsManager", true); - Set("SmSeats", Int32.MaxValue); - Set("SmServiceAccounts", Int32.MaxValue); - Set("Version", 15); //This is set to 15 to use AllowAdminAccessToAllCollectionItems can be changed to 13 to just use Secrets Manager - Set("Issued", DateTime.UtcNow); - Set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1)); - Set("Expires", DateTime.UtcNow.AddYears(100)); - Set("Trial", false); - Set("LicenseType", Enum.Parse(licenseTypeEnum, "Organization")); - Set("LimitCollectionCreationDeletion", true); //This will be used in the new version of BitWarden but can be applied now - Set("AllowAdminAccessToAllCollectionItems", true); - Set("UseRiskInsights", true); - Set("UseOrganizationDomains", true); - Set("UseAdminSponsoredFamilies", true); - Set("Hash", Convert.ToBase64String((Byte[])type.GetMethod("ComputeHash").Invoke(license, []))); - Set("Signature", Convert.ToBase64String((Byte[])type.GetMethod("Sign").Invoke(license, [cert]))); - - Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); - return; - - void Set(String name, Object value) + MethodInfo computeHash = type.GetMethod("ComputeHash"); + if (computeHash == null) { - type.GetProperty(name)?.SetValue(license, value); + Console.WriteLine("Could not find ComputeHash!"); + return; } + + MethodInfo sign = type.GetMethod("Sign"); + if (sign == null) + { + Console.WriteLine("Could not find sign!"); + return; + } + + Set(type, license, "LicenseKey", String.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); + Set(type, license, "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); + } +} \ No newline at end of file diff --git a/src/licenseGen/licenseGen.csproj b/src/licenseGen/licenseGen.csproj index 1d6ccd0..6e03496 100644 --- a/src/licenseGen/licenseGen.csproj +++ b/src/licenseGen/licenseGen.csproj @@ -1,14 +1,10 @@ - Exe net8.0 - - -