From a3e46f28a36f9f997b341ccec19ef19e0ddf82d9 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 21 Aug 2024 23:54:18 +0200 Subject: [PATCH] Refactor DataProtection setup to common extension class (#130) --- src/AliasVault.Admin/Program.cs | 28 +-------- src/AliasVault.Api/Program.cs | 27 +-------- .../Cryptography/DataProtectionExtensions.cs | 59 +++++++++++++++++++ 3 files changed, 61 insertions(+), 53 deletions(-) create mode 100644 src/Utilities/Cryptography/DataProtectionExtensions.cs diff --git a/src/AliasVault.Admin/Program.cs b/src/AliasVault.Admin/Program.cs index 8d2e2e4b9..311b5e2f5 100644 --- a/src/AliasVault.Admin/Program.cs +++ b/src/AliasVault.Admin/Program.cs @@ -8,7 +8,6 @@ using System.Data.Common; using System.Globalization; using System.Reflection; -using System.Security.Cryptography.X509Certificates; using AliasServerDb; using AliasVault.Admin; using AliasVault.Admin.Auth.Providers; @@ -17,9 +16,7 @@ using AliasVault.Admin.Services; using AliasVault.Logging; using Cryptography; using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; @@ -96,30 +93,7 @@ builder.Services.AddIdentityCore(options => .AddSignInManager() .AddDefaultTokenProviders(); -// Generate or load the certificate -X509Certificate2 cert; -string certPath = "../../certificates/AliasVault.DataProtection.pfx"; -string certPassword = Environment.GetEnvironmentVariable("DATA_PROTECTION_CERT_PASS") ?? throw new KeyNotFoundException("DATA_PROTECTION_CERT_PASS environment variable is not set."); -if (certPassword == "Development") -{ - // For development use local certificate so it doesn't interfere with Docker setup which uses a unique generated password. - certPath = Path.Combine(AppContext.BaseDirectory, "AliasVault.DataProtection.Development.pfx"); -} - -if (!File.Exists(certPath)) -{ - cert = CertificateGenerator.GeneratePfx("AliasVault.DataProtection", certPassword); - CertificateGenerator.SaveCertificateToFile(cert, certPassword, certPath); -} -else -{ - cert = new X509Certificate2(certPath, certPassword); -} - -builder.Services.AddDataProtection() - .ProtectKeysWithCertificate(cert) - .PersistKeysToDbContext() - .SetApplicationName("AliasVault.Admin"); +builder.Services.AddAliasVaultDataProtection("AliasVault.Admin"); builder.Services.Configure(options => { diff --git a/src/AliasVault.Api/Program.cs b/src/AliasVault.Api/Program.cs index e2efa1e62..b6a97abac 100644 --- a/src/AliasVault.Api/Program.cs +++ b/src/AliasVault.Api/Program.cs @@ -7,7 +7,6 @@ using System.Data.Common; using System.Reflection; -using System.Security.Cryptography.X509Certificates; using System.Text; using AliasServerDb; using AliasVault.Api.Jwt; @@ -16,7 +15,6 @@ using AliasVault.Shared.Providers.Time; using Asp.Versioning; using Cryptography; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; @@ -53,30 +51,7 @@ builder.Services.AddDbContextFactory((container, options) options.UseSqlite(connection).UseLazyLoadingProxies(); }); -// Generate or load the DataProtection certificate. -X509Certificate2 cert; -string certPath = "../../certificates/AliasVault.DataProtection.pfx"; -string certPassword = Environment.GetEnvironmentVariable("DATA_PROTECTION_CERT_PASS") ?? throw new KeyNotFoundException("DATA_PROTECTION_CERT_PASS environment variable is not set."); -if (certPassword == "Development") -{ - // For development use local certificate so it doesn't interfere with Docker setup which uses a unique generated password. - certPath = Path.Combine(AppContext.BaseDirectory, "AliasVault.DataProtection.Development.pfx"); -} - -if (!File.Exists(certPath)) -{ - cert = CertificateGenerator.GeneratePfx("AliasVault.DataProtection", certPassword); - CertificateGenerator.SaveCertificateToFile(cert, certPassword, certPath); -} -else -{ - cert = new X509Certificate2(certPath, certPassword); -} - -builder.Services.AddDataProtection() - .ProtectKeysWithCertificate(cert) - .PersistKeysToDbContext() - .SetApplicationName("AliasVault.Api"); +builder.Services.AddAliasVaultDataProtection("AliasVault.Api"); builder.Services.AddIdentity(options => { diff --git a/src/Utilities/Cryptography/DataProtectionExtensions.cs b/src/Utilities/Cryptography/DataProtectionExtensions.cs new file mode 100644 index 000000000..829a09030 --- /dev/null +++ b/src/Utilities/Cryptography/DataProtectionExtensions.cs @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace Cryptography; + +using System.Security.Cryptography.X509Certificates; +using AliasServerDb; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +/// +/// Helper utility to configure DataProtection for web projects. +/// +public static class DataProtectionExtensions +{ + /// + /// Setup .NET DataProtection to use common AliasVault settings with self-signed certificate. + /// + /// Services. + /// Application name. + /// IServiceCollection. + /// Thrown if environment variable is not set. + public static IServiceCollection AddAliasVaultDataProtection( + this IServiceCollection services, + string applicationName) + { + string certPassword = Environment.GetEnvironmentVariable("DATA_PROTECTION_CERT_PASS") + ?? throw new KeyNotFoundException("DATA_PROTECTION_CERT_PASS is not set in configuration or environment variables."); + + string certPath = "../../certificates/AliasVault.DataProtection.pfx"; + if (certPassword == "Development") + { + certPath = Path.Combine(AppContext.BaseDirectory, "AliasVault.DataProtection.Development.pfx"); + } + + X509Certificate2 cert; + if (!File.Exists(certPath)) + { + cert = CertificateGenerator.GeneratePfx("AliasVault.DataProtection", certPassword); + CertificateGenerator.SaveCertificateToFile(cert, certPassword, certPath); + } + else + { + cert = new X509Certificate2(certPath, certPassword); + } + + services.AddDataProtection() + .ProtectKeysWithCertificate(cert) + .PersistKeysToDbContext() + .SetApplicationName(applicationName); + + return services; + } +}