From 8def331bb6d9572e4467675f7cc72cb66b764833 Mon Sep 17 00:00:00 2001 From: Pascal Pothmann Date: Wed, 3 Jun 2026 21:57:45 +0200 Subject: [PATCH] Update to .NET 10.0 and fix certificate validation for Bitwarden server 2026.5.0 (#282) * Update to .NET 10.0 for Bitwarden server 2026.5.0 compatibility Bitwarden server 2026.5.0 ships with .NET 10.0 runtime only, breaking the fast-patch build. This commit updates all .NET projects and build pipelines to target net10.0 and the dotnet/sdk:10.0 image. Additionally: - Replace obsolete X509Certificate2(byte[]) constructors with X509CertificateLoader.LoadCertificate() / LoadPkcs12FromFile() to resolve SYSLIB0057 warnings introduced in .NET 9/10 - Add -certpbe AES-256-CBC -keypbe AES-256-CBC -macalg SHA256 to generate-keys.sh PKCS#12 export, fixing OpenSSL 3.x errors caused by the deprecated RC2-40-CBC legacy algorithm - Update FixRuntimeConfig fallback framework version to 10.0.0 Fixes #281 Signed-off-by: Pascal Pothmann <19438422+p0thi@users.noreply.github.com> * Fix certificate validation by replacing all thumbprint occurrences Bitwarden's LicensingService performs two validation checks: 1. Validates _creationCertificate thumbprint 2. Validates all certificates in _verificationCertificates The thumbprint constants are inlined at compile time, creating multiple Ldstr instructions in the IL code. The patcher was only replacing the first occurrence, causing the second validation to fail with: 'Invalid license verifying certificate.' This fix replaces ALL occurrences of the old thumbprint to ensure both validation checks pass. Fixes runtime error: 'Invalid license verifying certificate' --------- Signed-off-by: Pascal Pothmann <19438422+p0thi@users.noreply.github.com> Co-authored-by: Pascal Pothmann <19438422+p0thi@users.noreply.github.com> --- .keys/generate-keys.sh | 2 +- build.sh | 2 +- src/bitBetter/Dockerfile | 2 +- src/bitBetter/Program.cs | 25 ++++++++++++++++--------- src/bitBetter/bitBetter.csproj | 2 +- src/bitBetter/build.sh | 2 +- src/licenseGen/Dockerfile | 4 ++-- src/licenseGen/Program.cs | 8 ++++---- src/licenseGen/licenseGen.csproj | 2 +- 9 files changed, 28 insertions(+), 21 deletions(-) diff --git a/.keys/generate-keys.sh b/.keys/generate-keys.sh index d460c13..6e63146 100755 --- a/.keys/generate-keys.sh +++ b/.keys/generate-keys.sh @@ -15,6 +15,6 @@ DIR=`exec 2>/dev/null;(cd -- "$DIR") && cd -- "$DIR"|| cd "$DIR"; unset PWD; /us # 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 pkcs12 -export -out "$DIR/cert.pfx" -inkey "$DIR/key.pem" -in "$DIR/cert.pem" -passin pass:test -passout pass:test +openssl pkcs12 -export -out "$DIR/cert.pfx" -inkey "$DIR/key.pem" -in "$DIR/cert.pem" -passin pass:test -passout pass:test -certpbe AES-256-CBC -keypbe AES-256-CBC -macalg SHA256 ls diff --git a/build.sh b/build.sh index 5fcdafe..ac5e6c7 100755 --- a/build.sh +++ b/build.sh @@ -70,7 +70,7 @@ else docker run --rm \ -v "$DIR/src/bitBetter:/bitBetter" \ -w /bitBetter \ - mcr.microsoft.com/dotnet/sdk:8.0 sh build.sh + mcr.microsoft.com/dotnet/sdk:10.0 sh build.sh docker build \ --no-cache \ diff --git a/src/bitBetter/Dockerfile b/src/bitBetter/Dockerfile index b94f9b2..f8eb7a1 100644 --- a/src/bitBetter/Dockerfile +++ b/src/bitBetter/Dockerfile @@ -1,7 +1,7 @@ ARG BITWARDEN_TAG FROM ${BITWARDEN_TAG} -COPY bin/Release/net8.0/publish/* /bitBetter/ +COPY bin/Release/net10.0/publish/* /bitBetter/ COPY ./.keys/cert.cert /newLicensing.cer RUN set -e; set -x; \ diff --git a/src/bitBetter/Program.cs b/src/bitBetter/Program.cs index c81308e..2713104 100644 --- a/src/bitBetter/Program.cs +++ b/src/bitBetter/Program.cs @@ -66,8 +66,8 @@ namespace BitwardenSelfLicensor module.Resources.Add(new EmbeddedResource("Bit.Core.licensing.cer", existingRes.Attributes, certBytes)); module.Resources.Remove(existingRes); - var existingCert = new X509Certificate2(existingRes.GetResourceData()); - var newCert = new X509Certificate2(certBytes); + var existingCert = X509CertificateLoader.LoadCertificate(existingRes.GetResourceData()); + var newCert = X509CertificateLoader.LoadCertificate(certBytes); Console.WriteLine($"Old thumbprint: {existingCert.Thumbprint}"); Console.WriteLine($"New thumbprint: {newCert.Thumbprint}"); @@ -85,15 +85,22 @@ namespace BitwardenSelfLicensor // Use Contains() to handle the hidden Unicode LRM character (\u200E) that Bitwarden // prepends to the production thumbprint string literal in LicensingService.cs - var instToReplace = ctor.Body.Instructions + // Replace ALL occurrences since const fields are inlined at compile time and used in + // multiple validation checks (both _creationCertificate and _verificationCertificates) + var instructionsToReplace = ctor.Body.Instructions .Where(i => i.OpCode == OpCodes.Ldstr) - .FirstOrDefault(i => ((string)i.Operand) - .Contains(existingCert.Thumbprint, StringComparison.OrdinalIgnoreCase)); + .Where(i => ((string)i.Operand) + .Contains(existingCert.Thumbprint, StringComparison.OrdinalIgnoreCase)) + .ToList(); - if (instToReplace != null) + if (instructionsToReplace.Count > 0) { - Console.WriteLine($"Replacing thumbprint Ldstr: '{instToReplace.Operand}'"); - rewriter.Replace(instToReplace, Instruction.Create(OpCodes.Ldstr, newCert.Thumbprint)); + Console.WriteLine($"Found {instructionsToReplace.Count} thumbprint Ldstr instruction(s) to replace"); + foreach (var inst in instructionsToReplace) + { + Console.WriteLine($" Replacing: '{inst.Operand}'"); + rewriter.Replace(inst, Instruction.Create(OpCodes.Ldstr, newCert.Thumbprint)); + } } else { @@ -121,7 +128,7 @@ namespace BitwardenSelfLicensor // Derive framework name/version from the self-contained includedFrameworks before removing it string fwName = "Microsoft.AspNetCore.App"; - string fwVersion = "8.0.0"; + string fwVersion = "10.0.0"; if (opts["includedFrameworks"] is JsonArray included && included.Count > 0) { var first = included[0]!.AsObject(); diff --git a/src/bitBetter/bitBetter.csproj b/src/bitBetter/bitBetter.csproj index 5b4b8e2..a0988be 100644 --- a/src/bitBetter/bitBetter.csproj +++ b/src/bitBetter/bitBetter.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 diff --git a/src/bitBetter/build.sh b/src/bitBetter/build.sh index 6de15ca..44ff366 100755 --- a/src/bitBetter/build.sh +++ b/src/bitBetter/build.sh @@ -4,4 +4,4 @@ set -e set -x dotnet restore -dotnet publish -c Release -o bin/Release/net8.0/publish +dotnet publish -c Release -o bin/Release/net10.0/publish diff --git a/src/licenseGen/Dockerfile b/src/licenseGen/Dockerfile index 527ccde..be993ec 100644 --- a/src/licenseGen/Dockerfile +++ b/src/licenseGen/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /licenseGen @@ -12,6 +12,6 @@ RUN set -e; set -x; \ FROM bitbetter/api -COPY --from=build /licenseGen/bin/Release/net8.0/publish/* /app/ +COPY --from=build /licenseGen/bin/Release/net10.0/publish/* /app/ ENTRYPOINT [ "dotnet", "/app/licenseGen.dll", "--core", "/app/Core.dll", "--executable", "/app/Api", "--cert", "/cert.pfx" ] diff --git a/src/licenseGen/Program.cs b/src/licenseGen/Program.cs index 019e3b1..50589f2 100644 --- a/src/licenseGen/Program.cs +++ b/src/licenseGen/Program.cs @@ -141,7 +141,7 @@ namespace BitwardenSelfLicensor buff = Console.ReadLine(); if ( buff == "" || buff == "y" || buff == "Y" ) { - GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), GetCoreDllPath(), name, email, storage, guid, null); + GenerateUserLicense(X509CertificateLoader.LoadPkcs12FromFile(cert.Value(), "test"), GetCoreDllPath(), name, email, storage, guid, null); } else { @@ -155,7 +155,7 @@ namespace BitwardenSelfLicensor buff = Console.ReadLine(); if ( buff == "" || buff == "y" || buff == "Y" ) { - GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), GetCoreDllPath(), name, email, storage, installid, businessname, null); + GenerateOrgLicense(X509CertificateLoader.LoadPkcs12FromFile(cert.Value(), "test"), GetCoreDllPath(), name, email, storage, installid, businessname, null); } else { @@ -214,7 +214,7 @@ namespace BitwardenSelfLicensor storageShort = (short) parsedStorage; } - GenerateUserLicense(new X509Certificate2(cert.Value(), "test"), GetCoreDllPath(), name.Value, email.Value, storageShort, userId, key.Value); + GenerateUserLicense(X509CertificateLoader.LoadPkcs12FromFile(cert.Value(), "test"), GetCoreDllPath(), name.Value, email.Value, storageShort, userId, key.Value); return 0; }); @@ -269,7 +269,7 @@ namespace BitwardenSelfLicensor storageShort = (short) parsedStorage; } - GenerateOrgLicense(new X509Certificate2(cert.Value(), "test"), GetCoreDllPath(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value); + GenerateOrgLicense(X509CertificateLoader.LoadPkcs12FromFile(cert.Value(), "test"), GetCoreDllPath(), name.Value, email.Value, storageShort, installationId, businessName.Value, key.Value); return 0; }); diff --git a/src/licenseGen/licenseGen.csproj b/src/licenseGen/licenseGen.csproj index 4a11363..177d1cb 100644 --- a/src/licenseGen/licenseGen.csproj +++ b/src/licenseGen/licenseGen.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0