diff --git a/ConfigCrypter.Console/Options/CommandlineOptions.cs b/ConfigCrypter.Console/Options/CommandlineOptions.cs index 0057b13..bb82d7d 100644 --- a/ConfigCrypter.Console/Options/CommandlineOptions.cs +++ b/ConfigCrypter.Console/Options/CommandlineOptions.cs @@ -1,4 +1,5 @@ using CommandLine; +using Newtonsoft.Json; namespace ConfigCrypter.Console.Options { @@ -24,10 +25,14 @@ public class CommandlineOptions [Option("format", Default = ConfigFormat.Json, HelpText = "The format of the config file.")] public ConfigFormat ConfigFormat { get; set; } + + [Option('x', "separator", Required = false, HelpText = "Split the key (if the key has multiple value).", Default = ';')] + public char Separator { get; set; } + } public enum ConfigFormat { Json } -} +} \ No newline at end of file diff --git a/ConfigCrypter.Console/Program.cs b/ConfigCrypter.Console/Program.cs index 82b8a40..b1f7784 100644 --- a/ConfigCrypter.Console/Program.cs +++ b/ConfigCrypter.Console/Program.cs @@ -15,12 +15,12 @@ static void Main(string[] args) .WithParsed(opts => { var crypter = CreateCrypter(opts); - crypter.EncryptKeyInFile(opts.ConfigFile, opts.Key); + crypter.EncryptKeyInFile(opts.ConfigFile, opts.Key, opts.Separator); }) .WithParsed(opts => { var crypter = CreateCrypter(opts); - crypter.DecryptKeyInFile(opts.ConfigFile, opts.Key); + crypter.DecryptKeyInFile(opts.ConfigFile, opts.Key, opts.Separator); }); } diff --git a/ConfigCrypter.Tests/ConfigCrypters/Json/JsonConfigCrypterTests.cs b/ConfigCrypter.Tests/ConfigCrypters/Json/JsonConfigCrypterTests.cs index 37ac432..cb834ca 100644 --- a/ConfigCrypter.Tests/ConfigCrypters/Json/JsonConfigCrypterTests.cs +++ b/ConfigCrypter.Tests/ConfigCrypters/Json/JsonConfigCrypterTests.cs @@ -22,6 +22,52 @@ public void EncryptKey_WithValidJson_CallsEncryptStringOnCrypter() Assert.Equal("ValueToEncrypt_encrypted", parsedJson.Key); } + [Fact] + public void EncryptKeys_WithValidJson_CallsEncryptStringOnCrypter() + { + var crypterMock = Mocks.Crypter; + var jsonCrypter = new JsonConfigCrypter(crypterMock.Object); + var json = JsonConvert.SerializeObject(new TestAppSettings() { Key = "ValueToEncrypt", AdditionalKey = "AdditionalKeyToEncrypt" }); + + var encryptedJson = jsonCrypter.EncryptKey(json, "Key;AdditionalKey"); + var parsedJson = JsonConvert.DeserializeObject(encryptedJson); + + // Additionally we test if the test crypter does its job. + Assert.Equal("ValueToEncrypt_encrypted", parsedJson.Key); + Assert.Equal("AdditionalKeyToEncrypt_encrypted", parsedJson.AdditionalKey); + } + + [Theory] + [InlineData('|')] + [InlineData(',')] + public void EncryptKeys_WithDifferentSeparator_WithValidJson_CallsEncryptStringOnCrypter(char separator) + { + var crypterMock = Mocks.Crypter; + var jsonCrypter = new JsonConfigCrypter(crypterMock.Object); + var json = JsonConvert.SerializeObject(new TestAppSettings() { Key = "ValueToEncrypt", AdditionalKey = "AdditionalKeyToEncrypt" }); + + var encryptedJson = jsonCrypter.EncryptKey(json, $"Key{separator}AdditionalKey", separator); + var parsedJson = JsonConvert.DeserializeObject(encryptedJson); + + // Additionally we test if the test crypter does its job. + Assert.Equal("ValueToEncrypt_encrypted", parsedJson.Key); + Assert.Equal("AdditionalKeyToEncrypt_encrypted", parsedJson.AdditionalKey); + } + + [Fact] + public void EncryptKeys_WithEmptyFieldAndWithValidJson_CallsEncryptStringOnCrypter() + { + var crypterMock = Mocks.Crypter; + var jsonCrypter = new JsonConfigCrypter(crypterMock.Object); + var json = JsonConvert.SerializeObject(new TestAppSettings() { Key = "ValueToEncrypt" }); + + var encryptedJson = jsonCrypter.EncryptKey(json, "Key;;"); + var parsedJson = JsonConvert.DeserializeObject(encryptedJson); + + // Additionally we test if the test crypter does its job. + Assert.Equal("ValueToEncrypt_encrypted", parsedJson.Key); + } + [Fact] public void DecryptKey_WithValidJson_CallsDecryptStringOnCrypter() { @@ -36,6 +82,52 @@ public void DecryptKey_WithValidJson_CallsDecryptStringOnCrypter() Assert.Equal("ValueToEncrypt", parsedJson.Key); } + [Fact] + public void DecryptKeys_WithValidJson_CallsEncryptStringOnCrypter() + { + var crypterMock = Mocks.Crypter; + var jsonCrypter = new JsonConfigCrypter(crypterMock.Object); + var json = JsonConvert.SerializeObject(new TestAppSettings() { Key = "ValueToEncrypt_encrypted", AdditionalKey = "AdditionalKeyToEncrypt_encrypted" }); + + var encryptedJson = jsonCrypter.DecryptKey(json, "Key;AdditionalKey"); + var parsedJson = JsonConvert.DeserializeObject(encryptedJson); + + // Additionally we test if the test crypter does its job. + Assert.Equal("ValueToEncrypt", parsedJson.Key); + Assert.Equal("AdditionalKeyToEncrypt", parsedJson.AdditionalKey); + } + + [Theory] + [InlineData('|')] + [InlineData(',')] + public void DecryptKey_WithDifferentSeparator_WithValidJson_CallsEncryptStringOnCrypter(char separator) + { + var crypterMock = Mocks.Crypter; + var jsonCrypter = new JsonConfigCrypter(crypterMock.Object); + var json = JsonConvert.SerializeObject(new TestAppSettings() { Key = "ValueToEncrypt_encrypted", AdditionalKey = "AdditionalKeyToEncrypt_encrypted" }); + + var encryptedJson = jsonCrypter.DecryptKey(json, $"Key{separator}AdditionalKey", separator); + var parsedJson = JsonConvert.DeserializeObject(encryptedJson); + + // Additionally we test if the test crypter does its job. + Assert.Equal("ValueToEncrypt", parsedJson.Key); + Assert.Equal("AdditionalKeyToEncrypt", parsedJson.AdditionalKey); + } + + [Fact] + public void DecryptKeys_WithEmptyFieldAndWithValidJson_CallsEncryptStringOnCrypter() + { + var crypterMock = Mocks.Crypter; + var jsonCrypter = new JsonConfigCrypter(crypterMock.Object); + var json = JsonConvert.SerializeObject(new TestAppSettings() { Key = "ValueToEncrypt_encrypted" }); + + var encryptedJson = jsonCrypter.DecryptKey(json, "Key;;"); + var parsedJson = JsonConvert.DeserializeObject(encryptedJson); + + // Additionally we test if the test crypter does its job. + Assert.Equal("ValueToEncrypt", parsedJson.Key); + } + [Fact] public void Dispose_CallsDisposeOnCrypter() { @@ -47,4 +139,4 @@ public void Dispose_CallsDisposeOnCrypter() crypterMock.Verify(crypter => crypter.Dispose()); } } -} +} \ No newline at end of file diff --git a/ConfigCrypter.Tests/TestAppSettings.cs b/ConfigCrypter.Tests/TestAppSettings.cs index b07233f..acf299f 100644 --- a/ConfigCrypter.Tests/TestAppSettings.cs +++ b/ConfigCrypter.Tests/TestAppSettings.cs @@ -3,5 +3,6 @@ public class TestAppSettings { public string Key { get; set; } + public string AdditionalKey { get; set; } } -} +} \ No newline at end of file diff --git a/ConfigCrypter/ConfigCrypters/IConfigCrypter.cs b/ConfigCrypter/ConfigCrypters/IConfigCrypter.cs index b1b7560..144371a 100644 --- a/ConfigCrypter/ConfigCrypters/IConfigCrypter.cs +++ b/ConfigCrypter/ConfigCrypters/IConfigCrypter.cs @@ -14,7 +14,7 @@ public interface IConfigCrypter : IDisposable /// Key of the config entry. /// The content of the config file where the key has been decrypted. /// It up to the implementer how to interpret the format of the config key. - string DecryptKey(string configFileContent, string configKey); + string DecryptKey(string configFileContent, string configKey, char separators = ';'); /// /// Encrypts the key in the given content of a config file. @@ -23,6 +23,6 @@ public interface IConfigCrypter : IDisposable /// Key of the config entry. /// The content of the config file where the key has been encrypted. /// It up to the implementer how to interpret the format of the config key. - string EncryptKey(string configFileContent, string configKey); + string EncryptKey(string configFileContent, string configKey, char separators = ';'); } } \ No newline at end of file diff --git a/ConfigCrypter/ConfigCrypters/Json/JsonConfigCrypter.cs b/ConfigCrypter/ConfigCrypters/Json/JsonConfigCrypter.cs index 6fc28c8..9948d42 100644 --- a/ConfigCrypter/ConfigCrypters/Json/JsonConfigCrypter.cs +++ b/ConfigCrypter/ConfigCrypters/Json/JsonConfigCrypter.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using DevAttic.ConfigCrypter.Crypters; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -25,17 +26,28 @@ public JsonConfigCrypter(ICrypter crypter) /// Decrypts the key in the given content of a config file. /// /// String content of a config file. - /// Key of the config entry. The key has to be in JSONPath format. + /// Key of the config entry. The key has to be in JSONPath format. + /// Split the key if it has multiple value. /// The content of the config file where the key has been decrypted. - public string DecryptKey(string configFileContent, string configKey) + public string DecryptKey(string configFileContent, string configKeys, char separator = ';') { - var (parsedConfig, settingsToken) = ParseConfig(configFileContent, configKey); + var configKeyList = configKeys.Split(separator); - var encryptedValue = _crypter.DecryptString(settingsToken.Value()); - settingsToken.Replace(encryptedValue); - var newConfigContent = parsedConfig.ToString(Formatting.Indented); + var parsedJson = JObject.Parse(configFileContent); + foreach (var configKey in configKeyList) + { + if (string.IsNullOrWhiteSpace(configKey)) continue; + + var settingsToken = ParseConfig(parsedJson, configKey); + + var decryptedValue = _crypter.DecryptString(settingsToken.Value()); + settingsToken.Replace(decryptedValue); + } + + var newConfigContent = parsedJson.ToString(Formatting.Indented); return newConfigContent; + } public void Dispose() @@ -48,16 +60,27 @@ public void Dispose() /// Encrypts the key in the given content of a config file. /// /// String content of a config file. - /// Key of the config entry. The key has to be in JSONPath format. + /// Key of the config entry. The key has to be in JSONPath format. + /// Split the key by semicolon(;) if it has multiple value. /// The content of the config file where the key has been encrypted. - public string EncryptKey(string configFileContent, string configKey) + public string EncryptKey(string configFileContent, string configKeys, char separator = ';') { - var (parsedConfig, settingsToken) = ParseConfig(configFileContent, configKey); - var encryptedValue = _crypter.EncryptString(settingsToken.Value()); - settingsToken.Replace(encryptedValue); - var newConfigContent = parsedConfig.ToString(Formatting.Indented); + var configKeyList = configKeys.Split(separator); + + var parsedJson = JObject.Parse(configFileContent); + + foreach (var configKey in configKeyList) + { + if (string.IsNullOrWhiteSpace(configKey)) continue; + + var settingsToken = ParseConfig(parsedJson, configKey); + + var encryptedValue = _crypter.EncryptString(settingsToken.Value()); + settingsToken.Replace(encryptedValue); + } + var newConfigContent = parsedJson.ToString(Formatting.Indented); return newConfigContent; } @@ -69,17 +92,16 @@ protected virtual void Dispose(bool disposing) } } - private (JObject ParsedConfig, JToken Key) ParseConfig(string json, string configKey) + private JToken ParseConfig(JToken json, string configKey) { - var parsedJson = JObject.Parse(json); - var keyToken = parsedJson.SelectToken(configKey); + var keyToken = json.SelectToken(configKey); if (keyToken == null) { throw new InvalidOperationException($"The key {configKey} could not be found."); } - return (parsedJson, keyToken); + return keyToken; } } -} \ No newline at end of file +} diff --git a/ConfigCrypter/ConfigFileCrypter.cs b/ConfigCrypter/ConfigFileCrypter.cs index fd91529..a8dd6cf 100644 --- a/ConfigCrypter/ConfigFileCrypter.cs +++ b/ConfigCrypter/ConfigFileCrypter.cs @@ -30,10 +30,11 @@ public ConfigFileCrypter(IConfigCrypter configCrypter, ConfigFileCrypterOptions /// /// Path of the configuration file. /// Key to decrypt, passed in a format the underlying config crypter understands. - public void DecryptKeyInFile(string filePath, string configKey) + /// Split the key if it has multiple value. + public void DecryptKeyInFile(string filePath, string configKey, char separator = ';') { var configContent = File.ReadAllText(filePath); - var decryptedConfigContent = _configCrypter.DecryptKey(configContent, configKey); + var decryptedConfigContent = _configCrypter.DecryptKey(configContent, configKey, separator); var targetFilePath = GetDestinationConfigPath(filePath, _options.DecryptedConfigPostfix); File.WriteAllText(targetFilePath, decryptedConfigContent); @@ -47,10 +48,11 @@ public void DecryptKeyInFile(string filePath, string configKey) /// /// Path of the configuration file. /// Key to encrypt, passed in a format the underlying config crypter understands. - public void EncryptKeyInFile(string filePath, string configKey) + /// Split the key if it has multiple value. + public void EncryptKeyInFile(string filePath, string configKey, char separator = ';') { var configContent = File.ReadAllText(filePath); - var encryptedConfigContent = _configCrypter.EncryptKey(configContent, configKey); + var encryptedConfigContent = _configCrypter.EncryptKey(configContent, configKey, separator); var targetFilePath = GetDestinationConfigPath(filePath, _options.EncryptedConfigPostfix); File.WriteAllText(targetFilePath, encryptedConfigContent); diff --git a/README.md b/README.md index a6039b0..45bea51 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,8 @@ If you now have your certificate you need to decide what keys you want to encryp ```json { "Nested": { - "KeyToEncrypt": "This will be encrypted" + "KeyToEncrypt": "This will be encrypted", + "AdditionalKeyToEncrypt": "This will be encrypted" } } ``` @@ -52,6 +53,15 @@ If you want to prevent the creation of a new file you can simply pass --replace To decrypt the file again you can simply execute: `config-crypter decrypt -p c:\path\to\cert.pfx -f c:\path\to\config_encrypted.json -k "Nested.KeyToEncrypt"` +### Multiple Keys Encryption in Single Command +You can use semicolon ";" as a separator by default. Apart from that you can also specify the separator that you want to use. + +To encrypt our keys from above we simple execute: +`config-crypter encrypt -p c:\path\to\cert.pfx -f c:\path\to\config.json -k "Nested.KeyToEncrypt;Nested.AdditionalKeyToEncrypt"`. + +To decrypt the file again you can simply execute: +`config-crypter decrypt -p c:\path\to\cert.pfx -f c:\path\to\config_encrypted.json -k "Nested.KeyToEncrypt;Nested.AdditionalKeyToEncrypt"` + ## Command line arguments The following command line arguments can be passed for the encrypt and decrypt command. ``` @@ -60,6 +70,7 @@ The following command line arguments can be passed for the encrypt and decrypt c -k, --key Required. The key to encrypt in the config file. -f, --file Required. The path to the config file. -r, --replace (Default: false) Replaces the original file if passed as parameter. +-x, --separator (Default : ';') Split the key (if the key has multiple value). --format (Default: Json) The format of the config file. --help Display this help screen. --version Display version information. @@ -130,4 +141,4 @@ To generate a certificate you could use the following commands: `openssl req -new -x509 -nodes -sha1 -days 365 -key private.key > public.cer` -`openssl pkcs12 -export -in public.cer -inkey private.key -out cert.pfx` +`openssl pkcs12 -export -in public.cer -inkey private.key -out cert.pfx` \ No newline at end of file