Skip to content

Commit

Permalink
Merge pull request #27 from fededim/develop
Browse files Browse the repository at this point in the history
Version 1.0.10
  • Loading branch information
fededim authored Jun 3, 2024
2 parents e7b69c6 + f1f0771 commit 04d2a35
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"DateTime": "2018-01-01T12:34:56.789",
"IntArray": [ 4, 5, 6 ],
"Nullable": {
"DateTime": "Protect:{2016-10-01T20:34:56.789Z}",
"Double": "Protect:{123.456}",
"DoubleArray": [ "2.71", "Protect:{3.14}", "6.67", "Protect:{%customSubPurpose%}:{3.14}" ],
"Int": "Protect:{98765}",
"Bool": "Protect:{true}"
"DateTime": "Protect:{2016-10-01T20:34:56.789Z}", // protecting datetime
"Double": "Protect:{123.456}", // protecting float
"DoubleArray": [ "2.71", "Protect:{3.14}", "6.67", "Protect:{%customSubPurpose%}:{3.14}" ], // example of array element encryption and per configuration key subpurpose
"Int": "Protect:{98765}", // protecting integer
"Bool": "Protect:{true}" //protecting boolean
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"DateTime": "2016-01-01T12:34:56.789",
"IntArray": [ 1, 2 ],
"PlainTextJsonSpecialCharacters": "\\!*+?|{[()^$.#\u0022",
"EncryptedJsonSpecialCharacters": "Protect:{\\!*+?|{[()^$.#\u0022}",
"EncryptedJsonSpecialCharacters": "Protect:{\\!*+?|{[()^$.#\u0022}", // protecting encoded characters
"Nullable": {
"Int": null,
"Double": null,
"Double": null, // sample null value
"Bool": null,
"DateTime": null,
"DoubleArray": [ 3.14, 2.71 ]
"DoubleArray": [ 3.14, 2.71 ]
},
"ConnectionStrings": {
"PlainTextConnectionString": "Data Source=localhost\\SQLEXPRESS; Initial Catalog=DB name; User ID=sa; Password=pass1234; MultipleActiveResultSets=True;",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<EncryptedXmlSecretKey>OtherProtect:{Xml Secret Key_\&gt;!*+?|{[()^$.#}</EncryptedXmlSecretKey>
<EncryptedXmlSecretKey>OtherProtect:{Xml Secret Key_\&gt;!*+?|{[()^$.#}</EncryptedXmlSecretKey> <!--Protecting encoded characters-->
<PlainTextXmlSecretKey>Xml Secret Key_\&gt;!*+?|{[()^$.#</PlainTextXmlSecretKey>
<TransientFaultHandlingOptions>
<Enabled>true</Enabled>
<AutoRetryDelay>OtherProtect:{00:00:07}</AutoRetryDelay>
<AutoRetryDelaySubPurpose>OtherProtect:{sUbPuRpOsE}:{00:00:07}</AutoRetryDelaySubPurpose>
<AutoRetryDelaySubPurpose>OtherProtect:{sUbPuRpOsE}:{00:00:07}</AutoRetryDelaySubPurpose> <!-- example of per configuration key subpurpose -->
</TransientFaultHandlingOptions>
<Logging>
<LogLevel>
<Default>Information</Default>
<Microsoft>OtherProtect:{Warning}</Microsoft>
<Microsoft>OtherProtect:{Warning}</Microsoft> <!--protecting string-->
</LogLevel>
</Logging>
<EmptyElement></EmptyElement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static class ConfigurationBuilderExtensions
/// This list gets processed in first-in first-out order (FIFO) and stops as soon as a matching is found. By default three types of custom processors are supported:<br/>
/// - One for JSON files (<see cref="JsonFileProtectOption"/> and <see cref="JsonFileProtectProcessor"/>)<br/>
/// - One for XML files (<see cref="XmlFileProtectOption"/> and <see cref="XmlFileProtectProcessor"/>)<br/>
/// - One for raw files (<see cref="RawFileProtectOption"/> and <see cref="RawFileProtectProcessor"/>)<br/>
/// - One for RAW files (<see cref="RawFileProtectOption"/> and <see cref="RawFileProtectProcessor"/>)<br/>
/// This list has a public getter so you can add any additional decoding function you want or replace an existing one for your needs.
/// </summary>
public static List<FilesProtectOptions> ProtectFilesOptions { get; private set; } = new List<FilesProtectOptions>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>disable</ImplicitUsings>

<PublishSingleFile>true</PublishSingleFile>
<Version>1.0.9</Version>
<Version>1.0.10</Version>
<Authors>Federico Di Marco &lt;[email protected]&gt;</Authors>
<Description>Fededim.Extensions.Configuration.Protected is an improved ConfigurationBuilder which allows partial or full encryption of configuration values stored inside any possible ConfigurationSource and fully integrated in the ASP.NET Core architecture. Basically, it implements a custom ConfigurationBuilder and a custom ConfigurationProvider defining a custom tokenization tag which whenever found decrypts the enclosed encrypted data using ASP.NET Core Data Protection API.</Description>
<Copyright>$([System.DateTime]::UtcNow.Year)</Copyright>
Expand Down Expand Up @@ -56,6 +56,10 @@

v1.0.9
- No changes, just a rebuild due to a misalignment with symbols.

v1.0.10
- Improvement: Allow the specification of JsonSerializationOptions for JsonFileProtectProcessor to tweak its settings (comments inside JSON files are now skipped by default)
- Improvement: Allow the specification of LoadOptions and SaveOptions for XmlFileProtectProcessor to tweak its settings
</PackageReleaseNotes>
</PropertyGroup>

Expand Down
69 changes: 56 additions & 13 deletions Fededim.Extensions.Configuration.Protected/ProtectFileProcessors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,22 @@
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Linq;
using System.Collections.Generic;

namespace Fededim.Extensions.Configuration.Protected
{
/// <summary>
/// This is the interface which must be implemented by a custom FileProtectProcessor. It contains a single method <see cref="ProtectFile"/> used to read, encrypt and return the encrypted file as string.
/// This is the interface which must be implemented by a custom FileProtectProcessor. It contains a single method <see cref="ProtectFile"/> used to decode, encrypt and re-encode the input file and return it as string.
/// </summary>
public interface IFileProtectProcessor
{
/// <summary>
/// This method actually implements a custom FileProtectProcessor which must read, encrypt and return the encrypted file as string.
/// This method actually implements a custom FileProtectProcessor which must decode, encrypt and re-encode the input file and return it as string.
/// </summary>
/// <param name="rawFileText">The is the raw input file as a string</param>
/// <param name="protectRegex">This is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data.</param>
/// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing encrypted base64 data as output</param>
/// <returns>the encrypted file as a string</returns>
/// <returns>the encrypted re-encoded file as a string</returns>
String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String> protectFunction);
}

Expand All @@ -40,7 +39,7 @@ public class FilesProtectOptions
public Regex FilenameRegex { get; private set; }

/// <summary>
/// Specifies the FileProtectProcessor class implementing the <see cref="IFileProtectProcessor"/> interface used to read, encrypt and return the encrypted file as string.
/// Specifies the FileProtectProcessor class implementing the <see cref="IFileProtectProcessor"/> interface used to decode, encrypt and re-encode the input file and return it as string.
/// </summary>
public IFileProtectProcessor FileProtectProcessor { get; private set; }

Expand All @@ -65,7 +64,7 @@ public class RawFileProtectProcessor : IFileProtectProcessor
/// <param name="rawFileText">The is the raw input file as a string</param>
/// <param name="protectRegex">This is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data.</param>
/// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing encrypted base64 data as output</param>
/// <returns>the encrypted file as a string</returns>
/// <returns>the encrypted re-encoded file as a string</returns>
public String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String> ProtectFunction)
{
if (protectRegex.IsMatch(rawFileText))
Expand All @@ -82,17 +81,40 @@ public String ProtectFile(String rawFileText, Regex protectRegex, Func<String, S
/// </summary>
public class JsonFileProtectProcessor : IFileProtectProcessor
{
protected JsonSerializerOptions JsonSerializerOptions { get; set; }
protected JsonNodeOptions JsonNodeOptions { get; set; }
protected JsonDocumentOptions JsonDocumentOptions { get; set; }


/// <summary>
/// JsonFileProtectProcessor constructor accepting a JsonSerializerOptions.
/// JSON files in contrast to XML files must not have comments according to the standard, but this constraint can be relaxed by setting JsonCommentHandling property in the <see cref="jsonSerializerOptions"/>
/// Since NET Core 3.1 JsonCommentHandling.Allow option always raises an exception, so we set by default to allow comments but to skip them (JsonCommentHandling.Skip).
/// Moreover JsonSerializer generates always strict JSON files, so it won't output any comments in the encrypted re-encoded file.
/// By default we set ReadCommentHandling = JsonCommentHandling.Skip and WriteIndented = true
/// </summary>
/// <param name="jsonSerializerOptions">a custom JsonSerializerOptions if you want to override the default one</param>
public JsonFileProtectProcessor(JsonSerializerOptions jsonSerializerOptions = null)
{
jsonSerializerOptions = jsonSerializerOptions ?? new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Skip, WriteIndented = true };

JsonSerializerOptions = jsonSerializerOptions;
JsonNodeOptions = new JsonNodeOptions { PropertyNameCaseInsensitive = jsonSerializerOptions.PropertyNameCaseInsensitive };
JsonDocumentOptions = new JsonDocumentOptions { CommentHandling = jsonSerializerOptions.ReadCommentHandling, AllowTrailingCommas = jsonSerializerOptions.AllowTrailingCommas, MaxDepth = jsonSerializerOptions.MaxDepth };
}


/// <summary>
/// Please see <see cref="IFileProtectProcessor.ProtectFile"/> interface
/// </summary>
/// <param name="rawFileText">The is the raw input file as a string</param>
/// <param name="protectRegex">This is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data.</param>
/// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing encrypted base64 data as output</param>
/// <returns>the encrypted file as a string</returns>
/// <returns>the encrypted re-encoded file as a string</returns>
public String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String> protectFunction)
{
// Loads the JSON file
var document = JsonNode.Parse(rawFileText);
var document = JsonNode.Parse(rawFileText, JsonNodeOptions, JsonDocumentOptions);

// extract and encrypts all string node values
// extraction must be done first because if you change any value while enumerating the collection it raises InvalidOperationException
Expand All @@ -119,10 +141,10 @@ public String ProtectFile(String rawFileText, Regex protectRegex, Func<String, S
}

// returns back the encrypted json file
return document.ToJsonString(new JsonSerializerOptions { WriteIndented = true });
return document.ToJsonString(JsonSerializerOptions);
}



/// <summary>
/// Helper method which extracts all string nodes from the JSON document. It implements a recursive DFS of the JSON parsed tree.
Expand Down Expand Up @@ -165,24 +187,45 @@ protected List<JsonNode> ExtractAllStringNodes(JsonNode node, Regex protectRegex
/// </summary>
public class XmlFileProtectProcessor : IFileProtectProcessor
{
protected LoadOptions LoadOptions { get; set; }
protected SaveOptions? SaveOptions { get; set; }

/// <summary>
/// XmlFileProtectProcessor constructor accepting a LoadOptions and/or SaveOptions.
/// By default we set LoadOptions.None and SaveOptions are instead taken from XML annotations (e.g. the first parent node with such SaveOptions annotation,
/// for XML annotations you can see GetSaveOptionsFromAnnotations method inside XLinq.cs <see href="https://github.com/microsoft/referencesource/blob/master/System.Xml.Linq/System/Xml/Linq/XLinq.cs#L1303-L1318"/> )
/// </summary>
/// <param name="loadOptions">a custom LoadOptions if you want to override the default one</param>
/// <param name="saveOptions">a custom SaveOptions if you want to override the default one</param>

public XmlFileProtectProcessor(LoadOptions loadOptions = LoadOptions.None, SaveOptions? saveOptions = null)
{
LoadOptions = loadOptions;
SaveOptions = saveOptions;
}

/// <summary>
/// Please see <see cref="IFileProtectProcessor.ProtectFile"/> interface
/// </summary>
/// <param name="rawFileText">The is the raw input file as a string</param>
/// <param name="protectRegex">This is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data.</param>
/// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing encrypted base64 data as output</param>
/// <returns>the encrypted file as a string</returns>
/// <returns>the encrypted re-encoded file as a string</returns>
public String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String> protectFunction)
{
// Loads the XML File
var document = XDocument.Parse(rawFileText);
var document = XDocument.Parse(rawFileText,LoadOptions);

ProtectXmlNodes(document.Root, protectRegex, protectFunction);

// returns back the encrypted xml file
using (var xmlBytes = new MemoryStream())
{
document.Save(xmlBytes);
if (SaveOptions.HasValue)
document.Save(xmlBytes, SaveOptions.Value); // use the SaveOptions specified in constructor
else
document.Save(xmlBytes); // use the SaveOptions from XML Annotations

return Encoding.UTF8.GetString(xmlBytes.ToArray());
}
}
Expand Down
5 changes: 5 additions & 0 deletions Fededim.Extensions.Configuration.Protected/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Fededim.Extensions.Configuration.Protected is an improved ConfigurationBuilder w
- Supports almost any NET framework (net6.0, netstandard2.0 and net462)
- Pluggable into any project with almost no changes to original NET / NET Core.
- Supports automatic re-decryption on configuration reload if underlying IConfigurationProvider supports it
- Supports per configuration value derived encryption subkey (called "subpurposes" in Data Protection API)

# How to Use

Expand Down Expand Up @@ -204,6 +205,10 @@ v1.0.8
v1.0.9
- No changes, just a rebuild due to a misalignment with symbols.

v1.0.10
- Improvement: Allow the specification of JsonSerializationOptions for JsonFileProtectProcessor to tweak its settings (comments inside JSON files are now skipped by default)
- Improvement: Allow the specification of LoadOptions and SaveOptions for XmlFileProtectProcessor to tweak its settings

# Detailed guide

You can find a [detailed article on CodeProject](https://www.codeproject.com/Articles/5374311/Fededim-Extensions-Configuration-Protected-the-ult) explaning the origin, how to use it and the main point of the implementation.
Expand Down

0 comments on commit 04d2a35

Please sign in to comment.