public void SetKey(string key, string value) { using var sman = SecretsManager.LoadStore(storeFile); sman.LoadKeyFromFile(keyFile); sman.Set(key, value); sman.SaveStore(storeFile); }
public void StoreAndLoad() { var storePath = Path.GetTempFileName(); var keyPath = Path.GetTempFileName(); using (var sman = SecretsManager.CreateStore()) { sman.GenerateKey(); foreach (var key in SecureData.Keys) { sman.Set(key, SecureData[key]); } sman.SaveStore(storePath); sman.ExportKey(keyPath); } using (var sman = SecretsManager.LoadStore(storePath)) { sman.LoadKeyFromFile(keyPath); foreach (var key in SecureData.Keys) { Assert.AreEqual(SecureData[key], sman.Retrieve(key), $"Retrieved data for key \"{key}\" does not match stored value!"); } } }
protected override void ProcessRecord() { try { if (File.Exists(StorePath)) { using (var sman = SecretsManager.LoadStore(StorePath)) { if (ParameterSetName == "KeyPath") { KeyPath = GetUnresolvedProviderPathFromPSPath(KeyPath); sman.LoadKeyFromFile(KeyPath); } if (ParameterSetName == "Password") { sman.LoadKeyFromPassword(Password); } sman.Set(Name, Value); StorePath = GetUnresolvedProviderPathFromPSPath(StorePath); sman.SaveStore(StorePath); } } } catch { //either the file doesn't exist, or it is a non-secretful file using (var sman = SecretsManager.CreateStore()) { if (ParameterSetName == "KeyPath") { KeyPath = GetUnresolvedProviderPathFromPSPath(KeyPath); sman.LoadKeyFromFile(KeyPath); } if (ParameterSetName == "Password") { sman.LoadKeyFromPassword(Password); } sman.Set(Name, Value); StorePath = GetUnresolvedProviderPathFromPSPath(StorePath); sman.SaveStore(StorePath); } } }
protected override void ProcessRecord() { StorePath = GetUnresolvedProviderPathFromPSPath(StorePath); using (var sman = SecretsManager.LoadStore(StorePath)) { if (All) { if (ParameterSetName == "KeyPath") { KeyPath = GetUnresolvedProviderPathFromPSPath(KeyPath); sman.LoadKeyFromFile(KeyPath); } if (ParameterSetName == "Password") { sman.LoadKeyFromPassword(Password); } Hashtable toReturn = new Hashtable(); foreach (var k in sman.Keys) { toReturn.Add(k, sman.Get(k)); } WriteObject(toReturn); } else { if (ParameterSetName == "KeyPath") { KeyPath = GetUnresolvedProviderPathFromPSPath(KeyPath); sman.LoadKeyFromFile(KeyPath); } if (ParameterSetName == "Password") { sman.LoadKeyFromPassword(Password); } WriteObject(sman.Get(Name)); } } }
/// <summary> /// Loads the SecureStore data from a stream. /// </summary> /// <param name="stream">The stream to read.</param> public override void Load(Stream stream) { var source = (SecureStoreConfigurationSource)Source; var dictionary = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); using (var manager = SecretsManager.LoadStore(stream)) { switch (source.KeyType) { case KeyType.File: // ref: https://github.com/aspnet/Configuration/blob/master/src/Config.FileExtensions/FileConfigurationProvider.cs#L48 var file = source.KeyFileProvider?.GetFileInfo(source.Key); if (file == null || !file.Exists) { var error = new StringBuilder( $"The configuration key file '{source.Key}' was not found and is not optional."); if (!string.IsNullOrEmpty(file?.PhysicalPath)) { error.Append($" The physical path is '{file.PhysicalPath}'."); } throw new FileNotFoundException(error.ToString()); } manager.LoadKeyFromFile(file.PhysicalPath); break; case KeyType.Password: manager.LoadKeyFromPassword(source.Key); break; default: throw new ArgumentOutOfRangeException(nameof(source.KeyType)); } foreach (var key in manager.Keys) { dictionary.Add(key, manager.Get(key)); } } Data = dictionary; }
public void StoreAndLoadStream() { var storePath = Path.GetTempFileName(); var keyPath = Path.GetTempFileName(); CreateTestStore(storePath, keyPath); using (var stream = new FileStream(storePath, FileMode.Open, FileAccess.Read)) { using (var sman = SecretsManager.LoadStore(stream)) { sman.LoadKeyFromFile(keyPath); foreach (var key in SecureData.Keys) { Assert.AreEqual(SecureData[key], sman.Retrieve(key), $"Retrieved data for key \"{key}\" does not match stored value!"); } } } }
public void CatchTamperedData() { const string password = "******"; string storePath = Path.GetTempFileName(); // Generate a valid store using (var sman = SecretsManager.CreateStore()) { sman.LoadKeyFromPassword(password); sman.Set("foo", "bar"); sman.SaveStore(storePath); } // Load the store contents into memory var fileData = File.ReadAllText(storePath); // We don't have access to the internal encrypted bytes payloads are // deserialized to, but we can just access it directly. var deserialized = JsonConvert.DeserializeObject <MockStore>(fileData); var bytes = deserialized.Secrets["foo"].Payload; // Tamper with the data var prng = new Random(); for (int i = 0; i < bytes.Length; ++i) { bytes[i] ^= (byte)prng.Next(); } // Write the changes back deserialized.Secrets["foo"].Payload = bytes; fileData = JsonConvert.SerializeObject(deserialized); File.WriteAllText(storePath, fileData); // Verify that tampering is caught using (var sman = SecretsManager.LoadStore(storePath)) { sman.LoadKeyFromPassword(password); Assert.ThrowsException <TamperedCipherTextException>(() => sman.Get("foo"), "Could not detect tampering with encrypted data!"); } }
public void CatchTamperedData() { const string password = "******"; string storePath = Path.GetTempFileName(); //generate a valid store using (var sman = SecretsManager.CreateStore()) { sman.LoadKeyFromPassword(password); sman.Set("foo", "bar"); sman.SaveStore(storePath); } //load the store contents into memory var fileData = File.ReadAllText(storePath); var deserialized = JsonConvert.DeserializeObject <dynamic>(fileData); string base64 = deserialized.Data.foo.Payload; var bytes = Convert.FromBase64String(base64); //tamper with the data var prng = new Random(); for (int i = 0; i < bytes.Length; ++i) { bytes[i] ^= (byte)prng.Next(); } //write the changes back deserialized.Data.foo.Payload = Convert.ToBase64String(bytes); fileData = JsonConvert.SerializeObject(deserialized); File.WriteAllText(storePath, fileData); //test decryption using (var sman = SecretsManager.LoadStore(storePath)) { sman.LoadKeyFromPassword(password); Assert.ThrowsException <TamperedCipherTextException>(() => sman.Retrieve("foo"), "Could not detect tampering with encrypted data!"); } }
public void EncryptionTest() { string storePath = Path.GetTempFileName(); using (var sman = SecretsManager.CreateStore()) { sman.LoadKeyFromPassword("password1"); sman.Set("foo", "bar"); sman.SaveStore(storePath); } using (var sman = SecretsManager.LoadStore(storePath)) { sman.LoadKeyFromPassword("password2"); string retrieved = null; try { retrieved = sman.Get("foo"); } catch { } Assert.AreNotEqual("bar", retrieved, "Retrieved encrypted data with wrong password!"); } }
static void Main(string[] args) { var argv = args; int argc = args.Length; string path = null; string password = null; string keyfile = null; bool delete = false; string key = null; string value = null; bool decryptAll = false; bool generate = false; DecryptFormat format = DecryptFormat.None; OptionSet globalOptions; var createOptions = new OptionSet { { "s|store=", "Path to the secrets store to be created", s => path = s }, { "p|password:"******"Secure the store with a key derived from a password (optionally provided in the command line)", p => password = p ?? "" }, { "f|keyfile=", "Secure the store with a randomly generated key, created at the provided path", f => keyfile = f }, { "g|generate", "Generate a new key and save it to the path specified by --keyfile", g => generate = g != null }, }; var updateOptions = new OptionSet { { "s|store=", "Path to the secrets store to be loaded", s => path = s }, { "p|password:"******"Decrypt the store with a key derived from a password (optionally provided in the command line)", p => password = p ?? "" }, { "f|keyfile=", "Decrypt the store with a keyfile, located at the provided path", f => keyfile = f }, { "k|key=", "The (new or existing) key of the (key, value) tuple to create or update", k => key = k }, { "v|value=", "The (new) value of the (key, value) tuple to create or update", v => value = v }, { "d|delete=", "The existing key of the (key, value) tuple to delete", k => { key = k; delete = true; } }, }; var decryptOptions = new OptionSet { { "s|store=", "Path to the secrets store to be loaded", s => path = s }, { "p|password:"******"Decrypt the store with a key derived from a password (optionally provided in the command line)", p => password = p ?? "" }, { "f|keyfile=", "Decrypt the store with a keyfile, located at the provided path", f => keyfile = f }, { "k|key=", "The (new or existing) key of the (key, value) tuple to create or update", k => key = k }, { "a|all", "Decrypt the entire contents of the store and print to stdout", a => decryptAll = a != null }, { "t|format=", "Specify the output format: json (default), text", t => { if (!Parse(t, out format)) { throw new ExitCodeException(1, "Unsupported format specified!"); } } } }; void printUsage() { Console.WriteLine($"ssclient [OPTIONS | [create|update|decrypt] OPTIONS]"); PrintOptions(null, globalOptions); Console.WriteLine(); PrintOptions("create", createOptions); PrintOptions("update", updateOptions); PrintOptions("decrypt", decryptOptions); } bool help = false; bool version = false; globalOptions = new OptionSet { { "h|help|?", "Show this help message and exit", h => help = true }, { "v|version", "Print version information and exit", h => version = true }, }; if (args.Length == 0) { Console.Write("Usage: "); printUsage(); Environment.Exit(1); } //only try to parse --help and --version globalOptions.Parse(new[] { args[0] }); void printVersion() { Console.WriteLine($"ssclient {AssemblyVersion} - SecureStore secrets manager client"); Console.WriteLine("Copyright NeoSmart Technologies 2017 - https://github.com/neosmart/SecureStore/"); } if (help) { printVersion(); Console.WriteLine(); Console.Write("Usage: "); printUsage(); Environment.Exit(0); } if (version) { printVersion(); Environment.Exit(0); } string command = args[0]; OptionSet options = null; switch (command) { case "create": options = createOptions; break; case "update": options = updateOptions; break; case "decrypt": options = decryptOptions; break; default: Console.WriteLine($"{command}: unsupported command!"); Console.WriteLine(); printUsage(); Environment.Exit(1); break; } int exitCode = 0; try { //we have no trailing parameters, but Mono.Options is dumb and does not treat --password PASSWORD //as an option:value tuple when password is defined as taking an optional value. //It instead requires --password=PASSWORD or --password:PASSWORD or -pPASSWORD var bareArguments = options.Parse(args.Skip(1)); bool standalonePassword = false; if (bareArguments.Count == 1 && password == "") { //check if this was the standalone password var possibles = new[] { "-p", "--password", "/p", "/password" }; for (int i = 1; i < argc; ++i) { if (!possibles.Contains(argv[i])) { continue; } //this was the password index if (argc > i + 1 && argv[i + 1] == bareArguments[0]) { password = bareArguments[0]; standalonePassword = true; } break; } } //Handle common parameters if (!standalonePassword && bareArguments.Count > 0) { Help("Invalid arguments!", command, options); } if (string.IsNullOrWhiteSpace(path)) { Help("A path to the secrets store is required!", command, options); } if (keyfile != null && password != null) { Help("Cannot specify both --password and --keyfile", command, options); } if (keyfile == null && password == null) { Help("Must specify either --password or --keyfile!", command, options); } if (password == "") { Console.Write("Password: "******"create") { if (generate && keyfile == null) { Help("--generate requires a path specified by --keyfile to export the newly created key to!", command, options); } sman = SecretsManager.CreateStore(); } else { if (command == "update") { if (delete && (key != null || value != null)) { Help("Cannot specify both --delete and --key or --value!", command, options); } else if (!delete && (key == null || value == null)) { Help("Must specify both --key and --value to update!", command, options); } } else if (command == "decrypt") { if (!decryptAll && string.IsNullOrWhiteSpace(key)) { Help("Either --all or --key KEY must be specified as an argument to decrypt!", command, options); } } sman = SecretsManager.LoadStore(path); } using (sman) { if (generate) { sman.GenerateKey(); sman.ExportKey(keyfile); } else if (keyfile != null) { sman.LoadKeyFromFile(keyfile); } else { sman.LoadKeyFromPassword(password); } var client = new Client(sman); switch (command) { case "create": client.Create(); break; case "update": if (delete) { client.Delete(key); } else { client.Update(key, value); } break; case "decrypt": if (!decryptAll) { if (format != DecryptFormat.None) { Help($"--format can only be used in conjunction with --all!", command, options); } client.Decrypt(key); } else { client.DecryptAll(format); } break; default: throw new NotImplementedException($"Case {command} not handled!"); } sman.SaveStore(path); } } catch (OptionException ex) { Console.WriteLine(ex.Message); exitCode = 1; } catch (ExitCodeException ex) { if (!string.IsNullOrWhiteSpace(ex.Message)) { Console.WriteLine(ex.Message); } exitCode = ex.ExitCode; } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); exitCode = 1; } Environment.Exit(exitCode); }
static void Main(string[] mainArgs) { var args = new List <string>(mainArgs); bool help = false; bool version = false; string path = null; string password = null; string keyfile = null; string secretName = null; string secretValue = null; bool decryptAll = false; DecryptFormat format = DecryptFormat.None; var globalOptions = new OptionSet { { "h|help|?", "Show this help message and exit", _ => help = true }, { "v|version", "Print version information and exit", _ => version = true }, { "s|store=", "Load secrets store from provided path", s => path = s }, { "p|password:"******"Prompt for decryption password", p => password = p ?? "" }, { "k|keyfile=", "Load decryption key from path", k => keyfile = k }, }; var options = new Dictionary <string, OptionSet>(); options["create"] = new OptionSet { { "h|help", "Show help for create command options", _ => help = true }, // password is : because automated systems may use the command line to specify passwords. This may change! { "p|password:"******"Secure store with a key derived from a password", p => password = p ?? "" }, { "k|keyfile=", "Path to load or save secure key from/to", k => keyfile = k }, }; options["delete"] = new OptionSet { { "h|help", "Show help for delete command options", _ => help = true }, { "s|store=", "Load secrets store from provided path", s => path = s }, // password is : because automated systems may use the command line to specify passwords. This may change! { "p|password:"******"Decrypt store with key derived from password", p => password = p ?? "" }, { "k|keyfile=", "Path to load secure key from", k => keyfile = k }, }; options["get"] = new OptionSet { { "h|help", "Show help for decryption command options", _ => help = true }, { "s|store=", "Load secrets store from provided path", s => path = s }, // password is : because automated systems may use the command line to specify passwords. This may change! { "p|password:"******"Decrypt store with key derived from password", p => password = p ?? "" }, { "k|keyfile=", "Path to load secure key from", k => keyfile = k }, { "a|all", "Decrypt the entire contents of the store and print to stdout", _ => decryptAll = true }, { "t|output-format=", "Specify the output format: json (default), text", t => { if (!Parse(t, out format)) { throw new ExitCodeException(1, "Unsupported format specified!"); } } } }; options["set"] = new OptionSet { { "h|help", "Show help for encryption command options", _ => help = true }, { "s|store=", "Load secrets store from provided path", s => path = s }, // password is : because automated systems may use the command line to specify passwords. This may change! { "p|password:"******"Decrypt store with key derived from password", p => password = p ?? "" }, { "k|keyfile=", "Path to load secure key from", k => keyfile = k }, }; void printUsage(TextWriter output) { output.WriteLine($"ssclient [FLAGS] [create|set|get|delete] OPTIONS"); PrintOptions(output, null, globalOptions); output.WriteLine(); foreach (var kv in options) { PrintOptions(output, kv.Key, kv.Value); } } if (args.Count == 0) { Console.Out.Write("Usage: "); printUsage(Console.Out); Environment.Exit(1); } // Only try to parse --help and --version globalOptions.Parse(args); void printVersion() { Console.Out.WriteLine($"ssclient {AssemblyVersion} - SecureStore secrets manager client"); Console.Out.WriteLine("Copyright NeoSmart Technologies 2017-2020 - https://github.com/neosmart/SecureStore/"); } if (help) { printVersion(); Console.Out.WriteLine(); Console.Out.Write("Usage: "); printUsage(Console.Out); Environment.Exit(0); } if (version) { printVersion(); Environment.Exit(0); } int commandIndex = args.FindIndex(arg => options.ContainsKey(arg)); if (commandIndex < 0) { printUsage(Console.Error); Environment.Exit(1); } string command = args[commandIndex]; args.RemoveAt(commandIndex); OptionSet parseOptions = options[command]; int exitCode = 0; try { // Mono.Options is dumb and does not treat --password PASSWORD // as an option:value tuple when password is defined as taking an optional value. // It instead requires --password=PASSWORD or --password:PASSWORD or -pPASSWORD var bareArguments = parseOptions.Parse(args); if (bareArguments.Count > 0 && password == string.Empty) { // Check if this was the standalone password var possibles = new[] { "-p", "--password", "/p", "/password" }; for (int i = 0; i < args.Count - 1; ++i) { if (!possibles.Contains(args[i])) { continue; } // This was the password index var bareIndex = bareArguments.FindIndex(arg => arg == args[i + 1]); if (bareIndex >= 0) { password = bareArguments[bareIndex]; bareArguments.RemoveAt(bareIndex); break; } } } // Consume remaining bare parameters before carrying out any actions so we can validate // there are no unexpected bare parameters. if (command == "create") { if (bareArguments.Count > 0 && string.IsNullOrEmpty(path)) { path = bareArguments[0]; bareArguments.RemoveAt(0); } } else if (command == "get" || command == "delete") { if (!decryptAll && bareArguments.Count != 1) { Console.Error.WriteLine("Expected the name of a single secret to look up or delete!"); Environment.Exit(1); } else if (!decryptAll) { secretName = bareArguments[0]; bareArguments.RemoveAt(0); } } else if (command == "set") { if (bareArguments.Count == 1 && bareArguments[0].Contains('=')) { var parts = bareArguments[0].Split('=', 2); secretName = parts[0]; secretValue = parts[1]; bareArguments.RemoveAt(0); } else if (bareArguments.Count == 2 && !bareArguments[1].Contains('=')) { secretName = bareArguments[0]; secretValue = bareArguments[1]; bareArguments.RemoveRange(0, 2); } else { Console.Error.WriteLine("Expected a single \"key=value\" or \"key\" \"value\" to set!"); Environment.Exit(1); } } // Handle common parameters // if (bareArguments.Count > 0) // { // Console.Error.Write($"BareArguments[0]: {bareArguments[0]}"); // Help(Console.Error, "Invalid arguments!", command, parseOptions); // } if (path is null) { // Help(Console.Error, "A path to the secrets store is required!", command, parseOptions); // Default to secrets.json rather than error out path = "secrets.json"; } if (string.IsNullOrWhiteSpace(path)) { Help(Console.Error, "A path to the secrets store is required!", command, parseOptions); } if (keyfile == null && password == null) { // Help(Console.Error, "Must specify either --password or --keyfile!", command, parseOptions); // Default to password mode instead of erroring out password = string.Empty; } // We need to differentiate between null (not set) and empty (empty) if (password == string.Empty) { if (command == "create") { while (string.IsNullOrWhiteSpace(password)) { Console.Write("New password: "******"Confirm password: "******"Password: "******"create") { if (password is null && string.IsNullOrWhiteSpace(keyfile)) { Console.Error.WriteLine("A newly created store must have one or both of --password and --keyfile specified"); Environment.Exit(1); } if (!string.IsNullOrWhiteSpace(password) && File.Exists(keyfile) && new FileInfo(keyfile).Length > 0) { Confirm($"Overwrite the existing contents of the key file at {keyfile} " + "with a key derived from the provided password? [yes/no]: "); } sman = SecretsManager.CreateStore(); } else { if (command == "get") { if (decryptAll && !string.IsNullOrWhiteSpace(secretName)) { Help(Console.Error, "Either --all or KEY must be specified as an argument to decrypt (not both)!", command, parseOptions); } } sman = SecretsManager.LoadStore(path); } using (sman) { if (!string.IsNullOrEmpty(password)) { sman.LoadKeyFromPassword(password); } if (command == "create") { if (File.Exists(path) && new FileInfo(path).Length > 0) { Confirm($"Overwrite existing store at {path}? [yes/no]: "); } if (password == null) { if (File.Exists(keyfile) && new FileInfo(keyfile).Length > 0) { sman.LoadKeyFromFile(keyfile); } else { sman.GenerateKey(); sman.ExportKey(keyfile); } } else if (!string.IsNullOrEmpty(keyfile)) { sman.ExportKey(keyfile); } } else if (password == null && keyfile != null) { sman.LoadKeyFromFile(keyfile); } var client = new Client(sman); switch (command) { case "create": client.Create(); break; case "delete": client.Delete(secretName); break; case "set": client.Update(secretName, secretValue); break; case "get": if (!decryptAll) { if (format != DecryptFormat.None) { Help(Console.Error, "--format can only be used in conjunction with --all!", command, parseOptions); } client.Decrypt(secretName); } else { client.DecryptAll(format); } break; default: throw new NotImplementedException($"Case {command} not handled!"); } sman.SaveStore(path); } } catch (OptionException ex) { Console.WriteLine(ex.Message); exitCode = 1; } catch (ExitCodeException ex) { if (!string.IsNullOrWhiteSpace(ex.Message)) { Console.WriteLine(ex.Message); } exitCode = ex.ExitCode; } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); exitCode = 1; } Environment.Exit(exitCode); }
public string ReadKey(string key) { using var sman = SecretsManager.LoadStore(storeFile); sman.LoadKeyFromFile(keyFile); return !sman.TryGetValue(key, out string value) ? "" : value; }
public bool CheckKey(string key) { using var sman = SecretsManager.LoadStore(storeFile); sman.LoadKeyFromFile(keyFile); return sman.TryGetValue(key, out string value); }