Compare commits

..

No commits in common. "29c6266948ba65e469a21ed1146111c5c738af1e" and "90e563e21f0814b9ddeb54da9a82b96ccaa4e50a" have entirely different histories.

8 changed files with 203 additions and 72 deletions

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
set -e
DIR=`dirname "$0"` DIR=`dirname "$0"`
DIR=`exec 2>/dev/null;(cd -- "$DIR") && cd -- "$DIR"|| cd "$DIR"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd` DIR=`exec 2>/dev/null;(cd -- "$DIR") && cd -- "$DIR"|| cd "$DIR"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd`
BW_VERSION=$(curl -sL https://go.btwrdn.co/bw-sh-versions | grep '^ *"'coreVersion'":' | awk -F\: '{ print $2 }' | sed -e 's/,$//' -e 's/^"//' -e 's/"$//') BW_VERSION=$(curl -sL https://go.btwrdn.co/bw-sh-versions | grep '^ *"'coreVersion'":' | awk -F\: '{ print $2 }' | sed -e 's/,$//' -e 's/^"//' -e 's/"$//')
@ -9,18 +9,14 @@ echo "Building BitBetter for BitWarden version $BW_VERSION"
# If there aren't any keys, generate them first. # If there aren't any keys, generate them first.
[ -e "$DIR/.keys/cert.cert" ] || "$DIR/.keys/generate-keys.sh" [ -e "$DIR/.keys/cert.cert" ] || "$DIR/.keys/generate-keys.sh"
# Prepare Bitwarden server repository [ -e "$DIR/src/bitBetter/.keys" ] || mkdir "$DIR/src/bitBetter/.keys"
rm -rf $DIR/server
git clone --branch "v${BW_VERSION}" --depth 1 https://github.com/bitwarden/server.git $DIR/server
# Replace certificate file and thumbprint cp "$DIR/.keys/cert.cert" "$DIR/src/bitBetter/.keys"
old_thumbprint=$(openssl x509 -fingerprint -noout -in $DIR/server/src/Core/licensing.cer | cut -d= -f2 | tr -d ':')
new_thumbprint=$(openssl x509 -fingerprint -noout -in $DIR/.keys/cert.cert | cut -d= -f2 | tr -d ':')
sed -i -e "s/$old_thumbprint/$new_thumbprint/g" $DIR/server/src/Core/Services/Implementations/LicensingService.cs
cp $DIR/.keys/cert.cert $DIR/server/src/Core/licensing.cer
docker build --no-cache --label com.bitwarden.product="bitbetter" $DIR/server -f $DIR/server/src/Api/Dockerfile -t bitbetter/api docker run --rm -v "$DIR/src/bitBetter:/bitBetter" -w=/bitBetter mcr.microsoft.com/dotnet/sdk:8.0 sh build.sh
docker build --no-cache --label com.bitwarden.product="bitbetter" $DIR/server -f $DIR/server/src/Identity/Dockerfile -t bitbetter/identity
docker build --no-cache --build-arg BITWARDEN_TAG=ghcr.io/bitwarden/api:$BW_VERSION --label com.bitwarden.product="bitbetter" -t bitbetter/api "$DIR/src/bitBetter" # --squash
docker build --no-cache --build-arg BITWARDEN_TAG=ghcr.io/bitwarden/identity:$BW_VERSION --label com.bitwarden.product="bitbetter" -t bitbetter/identity "$DIR/src/bitBetter" # --squash
docker tag bitbetter/api bitbetter/api:latest docker tag bitbetter/api bitbetter/api:latest
docker tag bitbetter/identity bitbetter/identity:latest docker tag bitbetter/identity bitbetter/identity:latest

11
src/bitBetter/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
ARG BITWARDEN_TAG
FROM ${BITWARDEN_TAG}
COPY bin/Release/net8.0/publish/* /bitBetter/
COPY ./.keys/cert.cert /newLicensing.cer
RUN set -e; set -x; \
dotnet /bitBetter/bitBetter.dll && \
mv /app/Core.dll /app/Core.orig.dll && \
mv /app/modified.dll /app/Core.dll && \
rm -rf /bitBetter && rm -rf /newLicensing.cer

93
src/bitBetter/Program.cs Normal file
View File

@ -0,0 +1,93 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
namespace bitwardenSelfLicensor
{
class Program
{
static int Main(string[] args)
{
string cerFile;
string corePath;
if(args.Length >= 2) {
cerFile = args[0];
corePath = args[1];
} else if (args.Length == 1) {
cerFile = args[0];
corePath = "/app/Core.dll";
}
else {
cerFile = "/newLicensing.cer";
corePath = "/app/Core.dll";
}
var module = ModuleDefinition.ReadModule(new MemoryStream(File.ReadAllBytes(corePath)));
var cert = File.ReadAllBytes(cerFile);
var x = module.Resources.OfType<EmbeddedResource>()
.Where(r => r.Name.Equals("Bit.Core.licensing.cer"))
.First();
Console.WriteLine(x.Name);
var e = new EmbeddedResource("Bit.Core.licensing.cer", x.Attributes, cert);
module.Resources.Add(e);
module.Resources.Remove(x);
var services = module.Types.Where(t => t.Namespace == "Bit.Core.Services");
var type = services.First(t => t.Name == "LicensingService");
var licensingType = type.Resolve();
var existingCert = new X509Certificate2(x.GetResourceData());
Console.WriteLine($"Existing Cert Thumbprint: {existingCert.Thumbprint}");
X509Certificate2 certificate = new X509Certificate2(cert);
Console.WriteLine($"New Cert Thumbprint: {certificate.Thumbprint}");
var ctor = licensingType.GetConstructors().Single();
var rewriter = ctor.Body.GetILProcessor();
var instToReplace =
ctor.Body.Instructions.Where(i => i.OpCode == OpCodes.Ldstr
&& string.Equals((string)i.Operand, existingCert.Thumbprint, StringComparison.InvariantCultureIgnoreCase))
.FirstOrDefault();
if(instToReplace != null) {
rewriter.Replace(instToReplace, Instruction.Create(OpCodes.Ldstr, certificate.Thumbprint));
}
else {
Console.WriteLine("Cant find inst");
}
// foreach (var inst in ctor.Body.Instructions)
// {
// Console.Write(inst.OpCode.Name + " " + inst.Operand?.GetType() + " = ");
// if(inst.OpCode.FlowControl == FlowControl.Call) {
// Console.WriteLine(inst.Operand);
// }
// else if(inst.OpCode == OpCodes.Ldstr) {
// Console.WriteLine(inst.Operand);
// }
// else {Console.WriteLine();}
// }
module.Write("modified.dll");
return 0;
}
}
}

View File

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

7
src/bitBetter/build.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
set -e
set -x
dotnet restore
dotnet publish

View File

@ -14,4 +14,4 @@ FROM bitbetter/api
COPY --from=build /licenseGen/bin/Release/net8.0/publish/* /app/ COPY --from=build /licenseGen/bin/Release/net8.0/publish/* /app/
ENTRYPOINT [ "dotnet", "/app/licenseGen.dll", "--core", "/app/Core.dll", "--executable", "/app/Api", "--cert", "/cert.pfx" ] ENTRYPOINT [ "dotnet", "/app/licenseGen.dll", "--core", "/app/Core.dll", "--cert", "/cert.pfx" ]

View File

@ -1,39 +1,37 @@
namespace BitwardenSelfLicensor
{
using Microsoft.Extensions.CommandLineUtils;
using Newtonsoft.Json;
using SingleFileExtractor.Core;
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.CommandLineUtils;
using Newtonsoft.Json;
public static class Program namespace bitwardenSelfLicensor
{ {
public static int Main(string[] args) class Program
{ {
var app = new CommandLineApplication(); static int Main(string[] args)
{
var app = new Microsoft.Extensions.CommandLineUtils.CommandLineApplication();
var cert = app.Option("--cert", "cert file", CommandOptionType.SingleValue); var cert = app.Option("--cert", "cert file", CommandOptionType.SingleValue);
var coreDll = app.Option("--core", "path to core dll", CommandOptionType.SingleValue); var coreDll = app.Option("--core", "path to core dll", CommandOptionType.SingleValue);
var exec = app.Option("--executable", "path to Bitwarden single file executable", CommandOptionType.SingleValue);
bool ExecExists() => File.Exists(exec.Value()); bool certExists()
bool CertExists() => File.Exists(cert.Value());
bool CoreExists() => File.Exists(coreDll.Value());
bool VerifyTopOptions() =>
!string.IsNullOrWhiteSpace(cert.Value()) &&
(!string.IsNullOrWhiteSpace(coreDll.Value()) || !string.IsNullOrWhiteSpace(exec.Value())) &&
CertExists() &&
(CoreExists() || ExecExists());
string GetExtractedDll()
{ {
var coreDllPath = Path.Combine("extract", "Core.dll"); return File.Exists(cert.Value());
var reader = new ExecutableReader(exec.Value()); }
reader.ExtractToDirectory("extract");
var fileInfo = new FileInfo(coreDllPath); bool coreExists()
return fileInfo.FullName; {
return File.Exists(coreDll.Value());
}
bool verifyTopOptions()
{
return !string.IsNullOrWhiteSpace(cert.Value()) &&
!string.IsNullOrWhiteSpace(coreDll.Value()) &&
certExists() && coreExists();
} }
string GetCoreDllPath() => CoreExists() ? coreDll.Value() : GetExtractedDll();
app.Command("interactive", config => app.Command("interactive", config =>
{ {
@ -45,11 +43,11 @@ namespace BitwardenSelfLicensor
config.OnExecute(() => config.OnExecute(() =>
{ {
if (!VerifyTopOptions()) if (!verifyTopOptions())
{ {
if (!ExecExists() && !string.IsNullOrWhiteSpace(exec.Value())) config.Error.WriteLine($"Cant find single file executable at: {exec.Value()}"); if (!coreExists()) config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}");
if (!CoreExists() && !string.IsNullOrWhiteSpace(coreDll.Value())) config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); if (!certExists()) config.Error.WriteLine($"Cant find certificate at: {cert.Value()}");
if (!CertExists()) config.Error.WriteLine($"Cant find certificate at: {cert.Value()}");
config.ShowHelp(); config.ShowHelp();
return 1; return 1;
} }
@ -94,7 +92,7 @@ namespace BitwardenSelfLicensor
WriteLineOver("Please enter a business name, default is BitBetter. [Business Name]:"); WriteLineOver("Please enter a business name, default is BitBetter. [Business Name]:");
buff = Console.ReadLine(); buff = Console.ReadLine();
if (buff == "") businessname = "BitBetter"; if (buff == "") businessname = "BitBetter";
else if (CheckBusinessName(buff)) businessname = buff; else if (checkBusinessName(buff)) businessname = buff;
} }
} }
else else
@ -107,14 +105,14 @@ namespace BitwardenSelfLicensor
{ {
WriteLineOver("Please provide the username this license will be registered to. [username]:"); WriteLineOver("Please provide the username this license will be registered to. [username]:");
buff = Console.ReadLine(); buff = Console.ReadLine();
if ( CheckUsername(buff) ) name = buff; if ( checkUsername(buff) ) name = buff;
} }
while (email == "") while (email == "")
{ {
WriteLineOver("Please provide the email address for the user " + name + ". [email]"); WriteLineOver("Please provide the email address for the user " + name + ". [email]");
buff = Console.ReadLine(); buff = Console.ReadLine();
if ( CheckEmail(buff) ) email = buff; if ( checkEmail(buff) ) email = buff;
} }
while (storage == 0) while (storage == 0)
@ -127,7 +125,7 @@ namespace BitwardenSelfLicensor
} }
else else
{ {
if (CheckStorage(buff)) storage = short.Parse(buff); if (checkStorage(buff)) storage = short.Parse(buff);
} }
} }
@ -137,7 +135,7 @@ namespace BitwardenSelfLicensor
buff = Console.ReadLine(); buff = Console.ReadLine();
if ( buff == "" || buff == "y" || buff == "Y" ) if ( buff == "" || buff == "y" || buff == "Y" )
{ {
GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), GetCoreDllPath(), name, email, storage, guid, null); GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name, email, storage, guid, null);
} }
else else
{ {
@ -151,7 +149,7 @@ namespace BitwardenSelfLicensor
buff = Console.ReadLine(); buff = Console.ReadLine();
if ( buff == "" || buff == "y" || buff == "Y" ) if ( buff == "" || buff == "y" || buff == "Y" )
{ {
GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), GetCoreDllPath(), name, email, storage, installid, businessname, null); GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), coreDll.Value(), name, email, storage, installid, businessname, null);
} }
else else
{ {
@ -175,11 +173,17 @@ namespace BitwardenSelfLicensor
config.OnExecute(() => config.OnExecute(() =>
{ {
if (!VerifyTopOptions()) if (!verifyTopOptions())
{ {
if (!ExecExists() && !string.IsNullOrWhiteSpace(exec.Value())) config.Error.WriteLine($"Cant find single file executable at: {exec.Value()}"); if (!coreExists())
if (!CoreExists() && !string.IsNullOrWhiteSpace(coreDll.Value())) config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); {
if (!CertExists()) config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}");
}
if (!certExists())
{
config.Error.WriteLine($"Cant find certificate at: {cert.Value()}");
}
config.ShowHelp(); config.ShowHelp();
return 1; return 1;
} }
@ -210,7 +214,7 @@ namespace BitwardenSelfLicensor
storageShort = (short) parsedStorage; storageShort = (short) parsedStorage;
} }
GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), GetCoreDllPath(), 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; return 0;
}); });
@ -227,11 +231,17 @@ namespace BitwardenSelfLicensor
config.OnExecute(() => config.OnExecute(() =>
{ {
if (!VerifyTopOptions()) if (!verifyTopOptions())
{ {
if (!ExecExists() && !string.IsNullOrWhiteSpace(exec.Value())) config.Error.WriteLine($"Cant find single file executable at: {exec.Value()}"); if (!coreExists())
if (!CoreExists() && !string.IsNullOrWhiteSpace(coreDll.Value())) config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}"); {
if (!CertExists()) config.Error.WriteLine($"Cant find certificate at: {cert.Value()}"); config.Error.WriteLine($"Cant find core dll at: {coreDll.Value()}");
}
if (!certExists())
{
config.Error.WriteLine($"Cant find certificate at: {cert.Value()}");
}
config.ShowHelp(); config.ShowHelp();
return 1; return 1;
} }
@ -265,7 +275,7 @@ namespace BitwardenSelfLicensor
storageShort = (short) parsedStorage; storageShort = (short) parsedStorage;
} }
GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), GetCoreDllPath(), 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; return 0;
}); });
@ -291,7 +301,7 @@ namespace BitwardenSelfLicensor
} }
// checkUsername Checks that the username is a valid username // checkUsername Checks that the username is a valid username
private static bool CheckUsername(string s) static bool checkUsername(string s)
{ {
if ( string.IsNullOrWhiteSpace(s) ) { if ( string.IsNullOrWhiteSpace(s) ) {
WriteLineOver("The username provided doesn't appear to be valid.\n"); WriteLineOver("The username provided doesn't appear to be valid.\n");
@ -301,7 +311,7 @@ namespace BitwardenSelfLicensor
} }
// checkBusinessName Checks that the Business Name is a valid username // checkBusinessName Checks that the Business Name is a valid username
private static bool CheckBusinessName(string s) static bool checkBusinessName(string s)
{ {
if ( string.IsNullOrWhiteSpace(s) ) { if ( string.IsNullOrWhiteSpace(s) ) {
WriteLineOver("The Business Name provided doesn't appear to be valid.\n"); WriteLineOver("The Business Name provided doesn't appear to be valid.\n");
@ -311,7 +321,7 @@ namespace BitwardenSelfLicensor
} }
// checkEmail Checks that the email address is a valid email address // checkEmail Checks that the email address is a valid email address
private static bool CheckEmail(string s) static bool checkEmail(string s)
{ {
if ( string.IsNullOrWhiteSpace(s) ) { if ( string.IsNullOrWhiteSpace(s) ) {
WriteLineOver("The email provided doesn't appear to be valid.\n"); WriteLineOver("The email provided doesn't appear to be valid.\n");
@ -321,7 +331,7 @@ namespace BitwardenSelfLicensor
} }
// checkStorage Checks that the storage is in a valid range // checkStorage Checks that the storage is in a valid range
private static bool CheckStorage(string s) static bool checkStorage(string s)
{ {
if (string.IsNullOrWhiteSpace(s)) if (string.IsNullOrWhiteSpace(s))
{ {
@ -337,16 +347,19 @@ namespace BitwardenSelfLicensor
} }
// WriteLineOver Writes a new line to console over last line. // WriteLineOver Writes a new line to console over last line.
private static void WriteLineOver(string s) static void WriteLineOver(string s)
{ {
Console.SetCursorPosition(0, Console.CursorTop -1); Console.SetCursorPosition(0, Console.CursorTop -1);
Console.WriteLine(s); Console.WriteLine(s);
} }
// WriteLine This wrapper is just here so that console writes all look similar. // WriteLine This wrapper is just here so that console writes all look similar.
private static void WriteLine(string s) => Console.WriteLine(s); static void WriteLine(string s)
{
Console.WriteLine(s);
}
private static void GenerateUserLicense(X509Certificate2 cert, string corePath, string userName, string email, short storage, Guid userId, string key) static void GenerateUserLicense(X509Certificate2 cert, string corePath, string userName, string email, short storage, Guid userId, string key)
{ {
var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath);
@ -379,7 +392,7 @@ namespace BitwardenSelfLicensor
Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented));
} }
private static void GenerateOrgLicense(X509Certificate2 cert, string corePath, string userName, string email, short storage, Guid instalId, string businessName, string key) static void GenerateOrgLicense(X509Certificate2 cert, string corePath, string userName, string email, short storage, Guid instalId, string businessName, string key)
{ {
var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath); var core = AssemblyLoadContext.Default.LoadFromAssemblyPath(corePath);

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@ -7,8 +7,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" /> <PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="SingleFileExtractor.Core" Version="2.3.0" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> <PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
</ItemGroup> </ItemGroup>