I tried to implement a custom JsonConfigurationProvider called 'CryptographyConfigProvider' that decrypts JSON from a stream if it is encrypted.
I inherited from the JsonConfigurationProvider and implemented a custom Load-method.
I use it like this in the Program.cs:
var configuration = new ConfigurationBuilder()
.AddEncryptedJsonFile($"appsettings.{enviromentValue}.json", optional: true, reloadOnChange: false)
.Build();
This call executes my custom implementation. (see below)
But after this ASP.NET Core tries to access the appsettings file again here:
webHostBuilder.Build().Run();
The exception indicates that the normal JsonConfigurationProvider is called and not my inherited class CryptographyConfigProvider.
System.FormatException: Could not parse the JSON file. Error on line number '0': 'EncryptedString'.
---> Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: S. Path '', line 0, position 0.
at Newtonsoft.Json.JsonTextReader.ParseValue()
at Newtonsoft.Json.Linq.JObject.Load(JsonReader reader, JsonLoadSettings settings)
at Microsoft.Extensions.Configuration.Json.JsonConfigurationFileParser.Parse(Stream input)
at Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider.Load(Stream stream)
--- End of inner exception stack trace ---
at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load(Boolean reload)
at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load()
at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
at Microsoft.AspNetCore.Hosting.WebHostBuilder.BuildCommonServices(AggregateException& hostingStartupErrors)
at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
at Main(String[] args) in Program.cs
Has someone an idea why ASP.NET Core is using the normal JsonConfigurationProvider?
Here is my implementation:
public static class DecryptionConfigProviderExtension
{
public static IConfigurationBuilder AddEncryptedJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange)
{
return builder.AddJsonFile(s =>
{
s.FileProvider = null;
s.Path = path;
s.Optional = optional;
s.ReloadOnChange = reloadOnChange;
s.ResolveFileProvider();
});
}
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<CryptographyConfigurationSource> configureSource) => builder.Add(configureSource);
}
public class CryptographyConfigurationSource : JsonConfigurationSource, IConfigurationSource
{
public override IConfigurationProvider Build(IConfigurationBuilder builder)
{
EnsureDefaults(builder);
return new CryptographyConfigProvider(this);
}
}
public class CryptographyConfigProvider : JsonConfigurationProvider
{
private const string EncryptionKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456";
private AesCryptography _aesCryptography;
public CryptographyConfigProvider(CryptographyConfigurationSource cryptographyConfigurationSource) : base(cryptographyConfigurationSource)
{
_aesCryptography = new AesCryptography();
}
public override void Load(Stream stream)
{
Data = UnencryptConfiguration(stream);
}
private IDictionary<string, string> UnencryptConfiguration(Stream stream)
{
var reader = new StreamReader(stream);
var text = reader.ReadToEnd();
var jsonString = DecryptIfEncrypted(text);
using (MemoryStream jsonStream = new MemoryStream())
{
var parser = new JsonConfigurationFileParser();
StreamWriter writer = new StreamWriter(jsonStream);
writer.Write(jsonString);
writer.Flush();
jsonStream.Position = 0;
return parser.Parse(jsonStream);
};
}
private string DecryptIfEncrypted(string text)
{
var jsonString = string.Empty;
try
{
jsonString = _aesCryptography.DecryptString(text, EncryptionKey);
}
catch
{
jsonString = text;
}
return jsonString;
}
}
Application configuration in ASP.NET Core is performed using one or more configuration providers. Configuration providers read configuration data from key-value pairs using a variety of configuration sources: Settings files, such as appsettings. json.
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } private IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { // TODO: Service configuration code here... } public void Configure(IApplicationBuilder app, ...
The appsettings. json file is generally used to store the application configuration settings such as database connection strings, any application scope global variables, and much other information.
As of .NET Core 2.0, appsettings.{env.EnvironmentName}.json
is loaded automatically for you. If you have encrypted it, then the framework will probably have an issue parsing it.
.ConfigureAppConfiguration((hostingContext, config) =>
{
...
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
...
MetaPackages/src/Microsoft.AspNetCore/WebHost.cs
I would try to name your file something else.
An alternative solution that my team recently implemented was to move secrets to app.config and use protected configuration to encrypt it. A custom configuration provider reads the application settings (e.g. Azure:ApiKey
) and supplies them to the Core framework.
Having to create custom providers and use old-school XML config files to handle encrypted settings is crazy. This should be handled by the framework, IMO.
In the mean time, my answer to this question is a pretty simple and straight-forward way to encrypt values in your settings files. It uses the existing JSON provider, preferred .Net Core encryption techniques, and is DI friendly.
Hope it helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With