Add missing licence options, bump licence version and add Token claim (#277)

* Add license options, bump version, add Token claim

Added the missing license options:
- `ExpirationWithoutGracePeriod`
- `UseAutomaticUserConfirmation`
- `UsePhisingBlocker`
- `UseDisableSmAdsForUsers`
- `UseMyItems`

Bumped the license version from 15 to 16 (`UseOrganizationDomains` is valid only with version 16).

Added `GenerateUserToken` and `GenerateOrgToken` helpers to create the expected `Token` claim that’s still missing in our licenses.

Signed-off-by: Lorenzo Moscati <lorenzo@moscati.page>

* Address Copilot review

- Capture `now = DateTime.UtcNow` once per license-generation call and pass it
  into GenerateUserToken/GenerateOrgToken, so all date fields in the JSON
  license and the embedded JWT derive from the same instant (fixes drift across
  separate DateTime.UtcNow calls)
- Move set("Token", ...) before ComputeHash/Sign in both GenerateUserLicense
  and GenerateOrgLicense so all fields are finalised before signing
- Add UseRiskInsights claim to the org JWT; confirmed present as a required
  claim in Bitwarden's OrganizationLicenseClaimsFactory

Version is intentionally excluded from both JWTs: neither UserLicenseClaimsFactory
nor OrganizationLicenseClaimsFactory generates it, and the claims-path VerifyData
never reads it.

Signed-off-by: Lorenzo Moscati <lorenzo@moscati.page>

---------

Signed-off-by: Lorenzo Moscati <lorenzo@moscati.page>
This commit is contained in:
Lorenzo Moscati
2026-04-12 08:04:48 +02:00
committed by GitHub
parent 31a08b7e89
commit dbaaa8b45d
2 changed files with 137 additions and 13 deletions

View File

@@ -1,11 +1,15 @@
namespace BitwardenSelfLicensor namespace BitwardenSelfLicensor
{ {
using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.CommandLineUtils;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json; using Newtonsoft.Json;
using SingleFileExtractor.Core; using SingleFileExtractor.Core;
using System; using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO; using System.IO;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
public static class Program public static class Program
@@ -20,7 +24,7 @@ namespace BitwardenSelfLicensor
bool ExecExists() => File.Exists(exec.Value()); bool ExecExists() => File.Exists(exec.Value());
bool CertExists() => File.Exists(cert.Value()); bool CertExists() => File.Exists(cert.Value());
bool CoreExists() => File.Exists(coreDll.Value()); bool CoreExists() => File.Exists(coreDll.Value());
bool VerifyTopOptions() => bool VerifyTopOptions() =>
!string.IsNullOrWhiteSpace(cert.Value()) && !string.IsNullOrWhiteSpace(cert.Value()) &&
(!string.IsNullOrWhiteSpace(coreDll.Value()) || !string.IsNullOrWhiteSpace(exec.Value())) && (!string.IsNullOrWhiteSpace(coreDll.Value()) || !string.IsNullOrWhiteSpace(exec.Value())) &&
CertExists() && CertExists() &&
@@ -34,7 +38,7 @@ namespace BitwardenSelfLicensor
return fileInfo.FullName; return fileInfo.FullName;
} }
string GetCoreDllPath() => CoreExists() ? coreDll.Value() : GetExtractedDll(); string GetCoreDllPath() => CoreExists() ? coreDll.Value() : GetExtractedDll();
app.Command("interactive", config => app.Command("interactive", config =>
{ {
string buff="", licensetype="", name="", email="", businessname=""; string buff="", licensetype="", name="", email="", businessname="";
@@ -360,19 +364,23 @@ namespace BitwardenSelfLicensor
type.GetProperty(name).SetValue(license, value); type.GetProperty(name).SetValue(license, value);
} }
set("LicenseKey", string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); var licenseKey = string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key;
set("LicenseKey", licenseKey);
set("Id", userId); set("Id", userId);
set("Name", userName); set("Name", userName);
set("Email", email); set("Email", email);
set("Premium", true); set("Premium", true);
set("MaxStorageGb", storage == 0 ? short.MaxValue : storage); set("MaxStorageGb", storage == 0 ? short.MaxValue : storage);
set("Version", 1); set("Version", 1);
set("Issued", DateTime.UtcNow); var now = DateTime.UtcNow;
set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1)); set("Issued", now);
set("Expires", DateTime.UtcNow.AddYears(100)); set("Refresh", now.AddYears(100).AddMonths(-1));
set("Expires", now.AddYears(100));
set("Trial", false); set("Trial", false);
set("LicenseType", Enum.Parse(licenseTypeEnum, "User")); set("LicenseType", Enum.Parse(licenseTypeEnum, "User"));
set("Token", GenerateUserToken(cert, userId, licenseKey, userName, email, storage, now));
set("Hash", Convert.ToBase64String((byte[])type.GetMethod("ComputeHash").Invoke(license, new object[0]))); 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 }))); set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert })));
@@ -394,12 +402,15 @@ namespace BitwardenSelfLicensor
type.GetProperty(name).SetValue(license, value); type.GetProperty(name).SetValue(license, value);
} }
set("LicenseKey", string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key); var licenseKey = string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString("n") : key;
var businessNameFinal = string.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName;
set("LicenseKey", licenseKey);
set("InstallationId", instalId); set("InstallationId", instalId);
set("Id", Guid.NewGuid()); set("Id", Guid.NewGuid());
set("Name", userName); set("Name", userName);
set("BillingEmail", email); set("BillingEmail", email);
set("BusinessName", string.IsNullOrWhiteSpace(businessName) ? "BitBetter" : businessName); set("BusinessName", businessNameFinal);
set("Enabled", true); set("Enabled", true);
set("Plan", "Enterprise (Annually)"); set("Plan", "Enterprise (Annually)");
set("PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually")); set("PlanType", Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
@@ -424,22 +435,134 @@ namespace BitwardenSelfLicensor
set("UseSecretsManager", true); set("UseSecretsManager", true);
set("SmSeats", int.MaxValue); set("SmSeats", int.MaxValue);
set("SmServiceAccounts", int.MaxValue); set("SmServiceAccounts", int.MaxValue);
set("Version", 15); //This is set to 15 to use AllowAdminAccessToAllCollectionItems can be changed to 13 to just use Secrets Manager set("Version", 16);
set("Issued", DateTime.UtcNow); var now = DateTime.UtcNow;
set("Refresh", DateTime.UtcNow.AddYears(100).AddMonths(-1)); set("Issued", now);
set("Expires", DateTime.UtcNow.AddYears(100)); set("Refresh", now.AddYears(100).AddMonths(-1));
set("Expires", now.AddYears(100));
set("ExpirationWithoutGracePeriod", now.AddYears(100));
set("Trial", false); set("Trial", false);
set("LicenseType", Enum.Parse(licenseTypeEnum, "Organization")); 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("LimitCollectionCreationDeletion", true);
set("AllowAdminAccessToAllCollectionItems", true); set("AllowAdminAccessToAllCollectionItems", true);
set("UseRiskInsights", true); set("UseRiskInsights", true);
set("UseOrganizationDomains", true); set("UseOrganizationDomains", true);
set("UseAdminSponsoredFamilies", true); set("UseAdminSponsoredFamilies", true);
set("UseAutomaticUserConfirmation", true);
set("UsePhishingBlocker", true);
set("UseDisableSmAdsForUsers", true);
set("UseMyItems", true);
var orgId = (Guid)type.GetProperty("Id").GetValue(license);
set("Token", GenerateOrgToken(cert, orgId, instalId, licenseKey, email, businessNameFinal, userName, storage, planTypeEnum, now));
set("Hash", Convert.ToBase64String((byte[])type.GetMethod("ComputeHash").Invoke(license, new object[0]))); 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 }))); set("Signature", Convert.ToBase64String((byte[])type.GetMethod("Sign").Invoke(license, new object[] { cert })));
Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented)); Console.WriteLine(JsonConvert.SerializeObject(license, Formatting.Indented));
} }
private static string GenerateUserToken(X509Certificate2 cert, Guid userId, string licenseKey, string name, string email, short maxStorageGb, DateTime now)
{
var secKey = new X509SecurityKey(cert);
var creds = new SigningCredentials(secKey, SecurityAlgorithms.RsaSha256);
var expires = now.AddYears(100);
var claims = new List<Claim>
{
new Claim("LicenseType", "User"),
new Claim("LicenseKey", licenseKey),
new Claim("Id", userId.ToString()),
new Claim("Name", name),
new Claim("Email", email),
new Claim("Premium", "true"),
new Claim("MaxStorageGb", (maxStorageGb == 0 ? short.MaxValue : maxStorageGb).ToString()),
new Claim("Trial", "false"),
new Claim("Issued", now.ToString("o")),
new Claim("Expires", expires.ToString("o")),
new Claim("Refresh", now.AddYears(100).AddMonths(-1).ToString("o")),
};
var handler = new JwtSecurityTokenHandler();
var token = new JwtSecurityToken(
issuer: "bitwarden",
audience: $"user:{userId}",
claims: claims,
notBefore: now,
expires: expires,
signingCredentials: creds);
return handler.WriteToken(token);
}
private static string GenerateOrgToken(X509Certificate2 cert, Guid orgId, Guid installationId, string licenseKey, string billingEmail, string businessName, string name, short maxStorageGb, Type planTypeEnum, DateTime now)
{
var secKey = new X509SecurityKey(cert);
var creds = new SigningCredentials(secKey, SecurityAlgorithms.RsaSha256);
var expires = now.AddYears(100);
// Resolve the integer value of EnterpriseAnnually from the runtime enum
var planTypeInt = Convert.ToInt32(Enum.Parse(planTypeEnum, "EnterpriseAnnually"));
var claims = new List<Claim>
{
new Claim("LicenseType", "Organization"),
new Claim("LicenseKey", licenseKey),
new Claim("InstallationId", installationId.ToString()),
new Claim("Id", orgId.ToString()),
new Claim("Name", name),
new Claim("BillingEmail", billingEmail),
new Claim("BusinessName", businessName),
new Claim("Enabled", "true"),
new Claim("Plan", "Enterprise (Annually)"),
new Claim("PlanType", planTypeInt.ToString()),
new Claim("Seats", int.MaxValue.ToString()),
new Claim("MaxCollections", short.MaxValue.ToString()),
new Claim("MaxStorageGb", (maxStorageGb == 0 ? short.MaxValue : maxStorageGb).ToString()),
new Claim("SelfHost", "true"),
new Claim("UsersGetPremium", "true"),
new Claim("UseGroups", "true"),
new Claim("UseDirectory", "true"),
new Claim("UseEvents", "true"),
new Claim("UseTotp", "true"),
new Claim("Use2fa", "true"),
new Claim("UseApi", "true"),
new Claim("UsePolicies", "true"),
new Claim("UseSso", "true"),
new Claim("UseResetPassword", "true"),
new Claim("UseKeyConnector", "true"),
new Claim("UseScim", "true"),
new Claim("UseCustomPermissions", "true"),
new Claim("UsePasswordManager", "true"),
new Claim("UseSecretsManager", "true"),
new Claim("SmSeats", int.MaxValue.ToString()),
new Claim("SmServiceAccounts", int.MaxValue.ToString()),
new Claim("UseRiskInsights", "true"),
new Claim("UseAdminSponsoredFamilies", "true"),
new Claim("UseOrganizationDomains", "true"),
new Claim("UseAutomaticUserConfirmation", "true"),
new Claim("UseDisableSmAdsForUsers", "true"),
new Claim("UsePhishingBlocker", "true"),
new Claim("UseMyItems", "true"),
new Claim("LimitCollectionCreationDeletion", "true"),
new Claim("AllowAdminAccessToAllCollectionItems", "true"),
new Claim("Trial", "false"),
new Claim("Issued", now.ToString("o")),
new Claim("Expires", expires.ToString("o")),
new Claim("Refresh", now.AddYears(100).AddMonths(-1).ToString("o")),
new Claim("ExpirationWithoutGracePeriod", expires.ToString("o")),
};
var handler = new JwtSecurityTokenHandler();
var token = new JwtSecurityToken(
issuer: "bitwarden",
audience: $"organization:{orgId}",
claims: claims,
notBefore: now,
expires: expires,
signingCredentials: creds);
return handler.WriteToken(token);
}
} }
} }

View File

@@ -10,6 +10,7 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SingleFileExtractor.Core" Version="2.3.0" /> <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" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>