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!");
                }
            }
        }
Esempio n. 2
0
        public void LoadMultipleKeys()
        {
            string exportedKeyPath = Path.GetTempFileName();

            using (var sman = SecretsManager.CreateStore())
            {
                sman.GenerateKey();

                //export to re-use later
                sman.ExportKey(exportedKeyPath);

                Assert.ThrowsException <KeyAlreadyLoadedException>(() => sman.GenerateKey());
                Assert.ThrowsException <KeyAlreadyLoadedException>(() => sman.LoadKeyFromFile(exportedKeyPath));
                Assert.ThrowsException <KeyAlreadyLoadedException>(() => sman.LoadKeyFromPassword("password"));
            }

            using (var sman = SecretsManager.CreateStore())
            {
                sman.LoadKeyFromPassword("password");
                Assert.ThrowsException <KeyAlreadyLoadedException>(() => sman.GenerateKey());
                Assert.ThrowsException <KeyAlreadyLoadedException>(() => sman.LoadKeyFromFile(exportedKeyPath));
                Assert.ThrowsException <KeyAlreadyLoadedException>(() => sman.LoadKeyFromPassword("password"));
            }

            using (var sman = SecretsManager.CreateStore())
            {
                sman.LoadKeyFromFile(exportedKeyPath);
                Assert.ThrowsException <KeyAlreadyLoadedException>(() => sman.GenerateKey());
                Assert.ThrowsException <KeyAlreadyLoadedException>(() => sman.LoadKeyFromFile(exportedKeyPath));
                Assert.ThrowsException <KeyAlreadyLoadedException>(() => sman.LoadKeyFromPassword("password"));
            }
        }
Esempio n. 3
0
        public void KeyDuplicationTest()
        {
            string path1 = Path.GetTempFileName();
            string path2 = Path.GetTempFileName();

            using (var sman = SecretsManager.CreateStore())
            {
                sman.GenerateKey();
                sman.ExportKey(path1);
            }

            using (var sman = SecretsManager.CreateStore())
            {
                sman.GenerateKey();
                sman.ExportKey(path2);
            }

            // Verify that keys are not the same
            var key1 = File.ReadAllBytes(path1);
            var key2 = File.ReadAllBytes(path2);

            Assert.IsTrue(key1.Length == key2.Length, "Generated key lengths differ!");
            Assert.IsTrue(key1.Length != 0, "A zero-length keyfile was created!");
            Assert.IsFalse(key1.SequenceEqual(key2));
        }
Esempio n. 4
0
        protected override void ProcessRecord()
        {
            KeyPath = GetUnresolvedProviderPathFromPSPath(KeyPath);

            using (var sman = SecretsManager.CreateStore())
            {
                sman.GenerateKey();
                sman.ExportKey(KeyPath);
            }
        }
Esempio n. 5
0
        public SecureStore(string filePath = "secure")
        {
            keyFile = string.Concat(filePath, ".key");
            storeFile = string.Concat(filePath, ".store");

            if (File.Exists(storeFile)) return;

            using var sman = SecretsManager.CreateStore();
            sman.GenerateKey();
            sman.ExportKey(keyFile);
            sman.SaveStore(storeFile);
        }
        public void ExportNewKey()
        {
            var keyPath = Path.GetTempFileName();

            using (var sman = SecretsManager.CreateStore())
            {
                sman.GenerateKey();
                sman.ExportKey(keyPath);
            }

            Assert.IsTrue(File.Exists(keyPath));
            Assert.AreNotEqual(0, new FileInfo(keyPath).Length, "Exported key is zero bytes!");
        }
        public void CreateStore()
        {
            var storePath = Path.GetTempFileName();

            using (var sman = SecretsManager.CreateStore())
            {
                sman.GenerateKey();
                sman.SaveStore(storePath);
            }

            Assert.IsTrue(File.Exists(storePath));
            Assert.AreNotEqual(0, new FileInfo(storePath).Length, "Saved store is zero bytes!");
        }
Esempio n. 8
0
 private void CreateTestStore(string storePath, string keyPath)
 {
     using (var sman = SecretsManager.CreateStore())
     {
         sman.GenerateKey();
         foreach (var key in SecureData.Keys)
         {
             sman.Set(key, SecureData[key]);
         }
         sman.SaveStore(storePath);
         sman.ExportKey(keyPath);
     }
 }
Esempio n. 9
0
        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);
                }
            }
        }
        public static void CreateTestStore(string storePath, string keyPath = null)
        {
            using (var store = SecretsManager.CreateStore())
            {
                store.GenerateKey();
                foreach (var key in SecureData.Keys)
                {
                    store.Set(key, SecureData[key]);
                }

                store.SaveStore(storePath);

                if (!string.IsNullOrEmpty(keyPath))
                {
                    store.ExportKey(keyPath);
                }
            }
        }
Esempio n. 11
0
        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!");
            }
        }
Esempio n. 12
0
        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!");
            }
        }
Esempio n. 13
0
        private void CreateTestStore(string storePath, string key, KeyType type)
        {
            using (var sman = SecretsManager.CreateStore())
            {
                if (type == KeyType.Password)
                {
                    sman.LoadKeyFromPassword(key);
                }
                else
                {
                    sman.GenerateKey();
                }

                foreach (var secretKey in SecureData.Keys)
                {
                    sman.Set(secretKey, SecureData[secretKey]);
                }

                sman.SaveStore(storePath);
                sman.ExportKey(key);
            }
        }
Esempio n. 14
0
        public void PasswordSalting()
        {
            const string password = "******";
            string       keyPath1 = Path.GetTempFileName();
            string       keyPath2 = Path.GetTempFileName();

            using (var sman = SecretsManager.CreateStore())
            {
                sman.LoadKeyFromPassword(password);
                sman.ExportKey(keyPath1);
            }

            using (var sman = SecretsManager.CreateStore())
            {
                sman.LoadKeyFromPassword(password);
                sman.ExportKey(keyPath2);
            }

            var key1 = File.ReadAllBytes(keyPath1);
            var key2 = File.ReadAllBytes(keyPath2);

            Assert.IsTrue(key1.Length == key2.Length);
            Assert.IsFalse(key1.SequenceEqual(key2));
        }
Esempio n. 15
0
        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!");
            }
        }
Esempio n. 16
0
        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);
        }
Esempio n. 17
0
        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);
        }