diff --git a/DorkNet.Server/Auth/IdentityServerIntegration.cs b/DorkNet.Server/Auth/IdentityServerIntegration.cs index 6b7cede..cabe3d4 100644 --- a/DorkNet.Server/Auth/IdentityServerIntegration.cs +++ b/DorkNet.Server/Auth/IdentityServerIntegration.cs @@ -32,12 +32,26 @@ public sealed class IdentityServerSigningKeyProvider "JWT secret not configured. Set DORKNET_JWT_SECRET env var or Jwt:Secret in appsettings.Local.json."); CertificatePath = ResolveCertificatePath(config); - Certificate = LoadOrCreateCertificate(CertificatePath, config, _legacySecret); + var password = config["IdentityServer:SigningCertificatePassword"] ?? _legacySecret; + var certificateBase64 = config["IdentityServer:SigningCertificateBase64"]; + if (!string.IsNullOrWhiteSpace(certificateBase64)) + { + LoadedFromBase64 = true; + Certificate = LoadCertificateFromBase64(certificateBase64, password); + } + else + { + Certificate = LoadOrCreateCertificate(CertificatePath, password); + } SigningKey = new X509SecurityKey(Certificate); LegacyJwtKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_legacySecret)); } public string CertificatePath { get; } + public bool LoadedFromBase64 { get; } + public string CertificateSource => LoadedFromBase64 + ? "IdentityServer:SigningCertificateBase64" + : CertificatePath; public X509Certificate2 Certificate { get; } public SecurityKey SigningKey { get; } public SecurityKey LegacyJwtKey { get; } @@ -51,9 +65,16 @@ public sealed class IdentityServerSigningKeyProvider return path; } - private static X509Certificate2 LoadOrCreateCertificate(string path, IConfiguration config, string fallbackPassword) + private static X509Certificate2 LoadCertificateFromBase64(string certificateBase64, string password) + { + var flags = X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.Exportable; + var compact = new string(certificateBase64.Where(c => !char.IsWhiteSpace(c)).ToArray()); + var bytes = Convert.FromBase64String(compact); + return new X509Certificate2(bytes, password, flags); + } + + private static X509Certificate2 LoadOrCreateCertificate(string path, string password) { - var password = config["IdentityServer:SigningCertificatePassword"] ?? fallbackPassword; var flags = X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.Exportable; Directory.CreateDirectory(Path.GetDirectoryName(path)!); diff --git a/DorkNet.Server/Controllers/Admin/AdminController.cs b/DorkNet.Server/Controllers/Admin/AdminController.cs index 1b5f697..3df3fa9 100644 --- a/DorkNet.Server/Controllers/Admin/AdminController.cs +++ b/DorkNet.Server/Controllers/Admin/AdminController.cs @@ -1169,6 +1169,7 @@ public class AdminController( var client = RecRoomIdentityServerConfig.Clients[0]; var certPath = signingKeys.CertificatePath; var fullCertPath = Path.GetFullPath(certPath); + var certificateExists = signingKeys.LoadedFromBase64 || System.IO.File.Exists(fullCertPath); var licenseConfigured = !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("DUENDE_IDENTITYSERVER_LICENSE_KEY")) || !string.IsNullOrWhiteSpace(config["IdentityServer:LicenseKey"]); @@ -1194,8 +1195,10 @@ public class AdminController( }, Signing = new { + signingKeys.CertificateSource, + signingKeys.LoadedFromBase64, CertificatePath = fullCertPath, - CertificateExists = System.IO.File.Exists(fullCertPath), + CertificateExists = certificateExists, signingKeys.Certificate.Thumbprint, NotBeforeUtc = signingKeys.Certificate.NotBefore.ToUniversalTime(), NotAfterUtc = signingKeys.Certificate.NotAfter.ToUniversalTime(), diff --git a/DorkNet.Server/admin-ui/src/pages/IdentityServer.tsx b/DorkNet.Server/admin-ui/src/pages/IdentityServer.tsx index d361c8a..e06f240 100644 --- a/DorkNet.Server/admin-ui/src/pages/IdentityServer.tsx +++ b/DorkNet.Server/admin-ui/src/pages/IdentityServer.tsx @@ -20,6 +20,8 @@ interface IdentityServerSettings { refreshTokenExpiration: string; }; signing: { + certificateSource: string; + loadedFromBase64: boolean; certificatePath: string; certificateExists: boolean; thumbprint: string; @@ -139,7 +141,10 @@ export function IdentityServer() {