ASP.NET Core 6.0 - Data Protection Keys

Ken Haggerty
Created 01/10/2022 - Updated 01/12/2022 02:31 GMT

This article describes the default ASP.NET Core Data Protection conditions and alternate solutions to persist Data Protection keys. I will assume you have downloaded the ASP.NET Core 6.0 - Users With Device 2FA Project.

Users With Device 2FA Project and Article Series

This article series is about the implementation of ASP.NET Core 6.0 with Visual Studio 2022. The ASP.NET Core 6.0 - Users With Device 2FA Project (UWD2FAP) implements WebAuthn, also known as FIDO2, instead of authenticator apps for two-factor authentication (2FA). The project implements Bootstrap v5 and Bootstrap Native. After a user registers, they can enable 2FA with Windows Hello, Android Lock Screen, or a FIDO2 security key. If I had this project when I created KenHaggerty. Com three years ago, I would have started with this user authentication template. The latest version is published at Preview. KenHaggerty. Com. I encourage you to register and evaluate multiple 2FA devices in Manage Account > Two-Factor Authentication. Details, screenshots, and related articles can be found at ASP.NET Core 6.0 - Users With Device 2FA Project. The details page includes the version change log.

Updated with links as the articles become published.

This is a Works On My Machine ¯\_(ツ)_/¯ story. When I published KenHaggerty. Com three years ago, I had an issue where the Login's Remember Me function seemed to not work after deployment. Research found ASP.NET Core Data Protection. The issue seemed resolved after I implemented AddDataProtection with PersistKeysToFileSystem and set write permission on the deployed \dp-keys folder.

Startup.cs > ConfigureServices
services.AddDataProtection()
    .SetApplicationName($"kenhaggerty.com-{env.EnvironmentName}")
    .PersistKeysToFileSystem(new DirectoryInfo($@"{env.ContentRootPath}\dp-keys"));
Program.cs
builder.Services.AddDataProtection()
    .SetApplicationName($"kenhaggerty.com-{builder.Environment.EnvironmentName}")
    .PersistKeysToFileSystem(new DirectoryInfo($@"{builder.Environment.ContentRootPath}\dp-keys"));

When I recently upgraded KenHaggerty. Com to ASP.NET Core 6.0, I deleted the contents of the deployed folder then published the new .NET 6.0 project. I knew the cause of the "UnauthorizedAccessException: Access to the path *** is denied." errors was the write permission not set on the deployed \dp-keys folder. My hosting provider recently disabled self service file permissions. I disabled the DataProtection service extension which mitigated the UnauthorizedAccessException errors but the Login's Remember Me issue was back. I opened a support ticket requesting write permission on the \dp-keys folder and started researching alternative solutions. During the support ticket dialog, the tech suggested "Please try again now, we enabled load user profile with the pool.". This did not resolve the UnauthorizedAccessException errors but did resolve the need to override the default Data Protection. After the support tech set the write permission on the \dp-keys folder, I enabled the DataProtection service extension and continued researching MSDocs - ASP.NET Core DataProtection.

The UWD2FAP is published at Preview. KenHaggerty. Com and shares an Application Pool with KenHaggerty. Com. Since the support tech enabled load user profile with the pool, the published UWD2FAP no longer needs to implement the PersistKeysToFileSystem to mitigate the Remember Me issue. The ASP.NET Core data protection stack provides a simple, easy to use cryptographic API a developer can use to protect data, including key management and rotation. Here are a few of the highlights from the research for this article. Most with links to further reading.

What is ASP.NET Core Data Protection?

The data-protection system is a set of cryptography APIs used by ASP.NET Core to encrypt data that must be handled by an untrusted third-party. See Andrew Lock - An introduction to the Data Protection system in ASP.NET Core.

Conditional Defaults

The app attempts to detect its operational environment and handle key configuration on its own. If none of these conditions match, keys aren't persisted outside of the current process. When the process shuts down, all generated keys are lost. See MSDocs - Data Protection key management and lifetime in ASP.NET Core.

  1. Azure Apps

    Keys are persisted to the %HOME%\ASP.NET\DataProtection-Keys folder.

  2. User profile is available

    Keys are persisted to the %LOCALAPPDATA%\ASP.NET\DataProtection-Keys folder.

  3. Hosted in IIS

    Keys are persisted to the HKLM registry in a special registry key that's ACLed only to the worker process account.

Override Default

Unfortunately, most of the defaults won't work for you once you start running your application in production and scaling up your applications. Instead, you'll likely need to take a look at one of the alternative configuration approaches. See Andrew Lock - An introduction to the Data Protection system in ASP.NET Core.

  1. PersistKeysToAzureBlobStorage

    Keys are persisted to the %HOME%\ASP.NET\DataProtection-Keys folder.

  2. PersistKeysToFileSystem

    Keys are persisted to the %LOCALAPPDATA%\ASP.NET\DataProtection-Keys folder.

  3. PersistKeysToDbContext

    Keys are persisted to the HKLM registry in a special registry key that's ACLed only to the worker process account.

Keys Encrypted at Rest

The data protection system employs a discovery mechanism by default to determine how cryptographic keys should be encrypted at rest. The developer can override the discovery mechanism and manually specify how keys should be encrypted at rest. See MSDocs - Key encryption at rest in Windows and Azure using ASP.NET Core.

  1. Azure Key Vault

    To store keys in Azure Key Vault, configure the system with PersistKeysToAzureBlobStorage and ProtectKeysWithAzureKeyVault.

  2. Windows DPAPI

    When Windows DPAPI is used, key material is encrypted with CryptProtectData before being persisted to storage. DPAPI is an appropriate encryption mechanism for data that's never read outside of the current machine.

  3. X.509 certificate

    If the app is spread across multiple machines, it may be convenient to distribute a shared X.509 certificate across the machines and configure the hosted apps to use the certificate for encryption of keys at rest.

  4. Windows DPAPI-NG

    The principal is encoded as a protection descriptor rule, only the domain-joined user with the specified SID can decrypt the key ring.

  5. Certificate-based encryption with Windows DPAPI-NG

    If the app is running on Windows 8.1/Windows Server 2012 R2 or later, you can use Windows DPAPI-NG to perform certificate-based encryption. Use the rule descriptor string "CERTIFICATE=HashId:THUMBPRINT", where THUMBPRINT is the hex-encoded SHA1 thumbprint of the certificate.

  6. Custom key encryption

    If the in-box mechanisms aren't appropriate, the developer can specify their own key encryption mechanism by providing a custom IXmlEncryptor.

I had implemented PersistKeysToDbContext on KenHaggerty. Com when the support request was escalated to the server team and delayed. The PersistKeysToDbContext option requires the NuGet - Microsoft.AspNetCore.DataProtection.EntityFrameworkCore package, the DbContext must implement IDataProtectionKeyContext, and requires a DataProtectionKeys table in the database. See MSDocs - Key storage providers in ASP.NET Core - Entity Framework Core. This is a good option when the host is not a Windows server, or the keys need to be shared across multiple instances of a web app. After the support tech set the write permission on the \dp-keys folder, I re-implemented PersistKeysToFileSystem with ProtectKeysWithDpapi on KenHaggerty. Com.

ProtectKeysWithDpapi

Configures keys to be encrypted with Windows DPAPI before being persisted to storage. The encrypted key will only be decryptable by the current Windows user account.

It took a long day to configure and deploy a staging environment on my local IIS to understand how the load user profile with the pool made a difference. I had to set the Application Pool's Identity to a Windows user to recreate the behavior.

IIS Load User Profile

The ASP.NET Core 6.0 - Users With Device 2FA Project, ASP.NET Core 6.0 - Users Without Passwords Project, and ASP.NET Core 6.0 - Users Without Identity Project have OverrideDataProtection (formally EnableDataProtection), DataProtectionApplicationNamePrefix, and DataProtectionKeyPath settings.

AppSettings.cs
/// <summary>
/// Overrides the default Microsoft. AspNetCore. DataProtection with PersistKeysToFileSystem.
/// </summary>
/// <remarks>
/// Persists Log In and Remember Me after application restart.
/// Requires write permission on the ContentRootPath + DataProtectionKeyPath.
/// </remarks>
public static bool OverrideDataProtection { get; } = true;
/// <summary>
/// DataProtection ApplicationName prefix.
/// </summary>
/// <remarks>
/// The EnvironmentName is appended.
/// </remarks>
public static string DataProtectionApplicationNamePrefix { get; } = "UsersWithDevice2FA";
/// <summary>
/// Path to DataProtection key storage.
/// </summary>
public static string DataProtectionKeyPath { get; } = "/dp-keys";
Program.cs
if (AppSettings.OverrideDataProtection)
{
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        builder.Services.AddDataProtection()
            .SetApplicationName($"{AppSettings.DataProtectionApplicationNamePrefix}-{builder.Environment.EnvironmentName}")
            .PersistKeysToFileSystem(new DirectoryInfo($@"{builder.Environment.ContentRootPath}{AppSettings.DataProtectionKeyPath}"))
            .ProtectKeysWithDpapi();
    else
        builder.Services.AddDataProtection()
            .SetApplicationName($"{AppSettings.DataProtectionApplicationNamePrefix}-{builder.Environment.EnvironmentName}")
            .PersistKeysToFileSystem(new DirectoryInfo($@"{builder.Environment.ContentRootPath}{AppSettings.DataProtectionKeyPath}"));
}

Article Tags:

Authorization

Comment Count = 0

Please log in to comment.

Login Register
Logged in users receive web notifications.
Web Notifications