Implementing protection of a section in appsettings.json in .NET core

In .NET Framework - there is a method to protect sections in the configuration (app.config) file with DPAPI. It can be done e.g. by calling aspnet_regiss.exe with some parameters with the path to the web.config file. The application code would not require any changes in order to be able to decrypt seamlessly the protected values.

In .NET Core, however, there is no default way of doing it, so we need to add some special code to handle encryption.

Start by adding this extension method:

public static class IServiceCollectionExtensions {

   public static IServiceCollection AddProtectedConfiguration(this IServiceCollection services)
   {
        services
            .AddDataProtection()
            .PersistKeysToFileSystem(new DirectoryInfo(@"..\keys"))
            .ProtectKeysWithDpapi();

        return services;
   }

   public static IServiceCollection ConfigureProtected<TOptions>(
            this IServiceCollection services, IConfigurationSection section)
            where TOptions : class, new()
   {
        return services.AddSingleton(provider =>
        {
            var dataProtectionProvider = provider.GetRequiredService<IDataProtectionProvider>();
            section = new ProtectedConfigurationSection(dataProtectionProvider, section);
            var options = section.Get<TOptions>();
            return Microsoft.Extensions.Options.Options.Create(options);
        });

   }

   private class ProtectedConfigurationSection : IConfigurationSection
   {
        private readonly IDataProtectionProvider _dataProtectionProvider;
        private readonly IConfigurationSection _section;
        private readonly Lazy<IDataProtector> _protector;

        public ProtectedConfigurationSection(
            IDataProtectionProvider dataProtectionProvider,
            IConfigurationSection section)
        {
            _dataProtectionProvider = dataProtectionProvider;
            _section = section;
            //_protector = new Lazy<IDataProtector>(() => dataProtectionProvider.CreateProtector(section.Path));
            _protector = new Lazy<IDataProtector>(() => dataProtectionProvider.CreateProtector("protector"));

        }

        public IConfigurationSection GetSection(string key)
        {
            return new ProtectedConfigurationSection(_dataProtectionProvider, _section.GetSection(key));
        }

        public IEnumerable<IConfigurationSection> GetChildren()
        {
            return _section.GetChildren()
                    .Select(x => new ProtectedConfigurationSection(_dataProtectionProvider, x));
        }

        public IChangeToken GetReloadToken()
        {
            return _section.GetReloadToken();
        }

        public string this[string key]
        {
            get => GetProtectedValue(_section[key]);
            set => _section[key] = _protector.Value.Protect(value);
        }


        public string Key => _section.Key;
        public string Path => _section.Path;

        public string Value
        {
            get => GetProtectedValue(_section.Value);
            set => _section.Value = _protector.Value.Protect(value);
        }

        private string GetProtectedValue(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
                return null;

            return _protector.Value.Unprotect(value);
        }

    }
}

Add the class with the protected section definition:

public class ProtectedValues
{
    public string Secret1 { get; set; }
    public string Secret2 { get; set; }
}

Under service configuration add this:

services.AddProtectedConfiguration();
services.ConfigureProtected<ProtectedValues>(
    Configuration.GetSection("ProtectedValues"));

Inject these services wherever encryption is required.

IOptions<ProtectedValues> protectedValues
IDataProtectionProvider dataProtectionProvider

Create a utility or add special controller action to give a way of generating encrypted values:

[HttpGet("protect")]
public IActionResult ProtectValues([FromQuery] string data)
{
   if (!string.IsNullOrEmpty(data))
   {
       var protector = dataProtectionProvider
        .CreateProtector("protector");
       string encrypted = protector.Protect(data);
       return Ok(encrypted);
   }

   return Ok();
}



Post a Comment

Previous Post Next Post