public void TamperDetect() { // Verify that we can detect that the data has been tampered with. var vault = new NeonVault(GetPassword); using (var tempFolder = new TempFolder()) { var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); var targetPath = Path.Combine(tempFolder.Path, "target.txt"); File.WriteAllText(sourcePath, unencryptedText); vault.Encrypt(sourcePath, targetPath, "password-1"); using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } // Modify the last HEX digit in the target file and verify // that decryption fails. using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.ReadWrite)) { var encrypted = new byte[(int)target.Length]; target.Read(encrypted, 0, encrypted.Length); var lastHexDigit = (char)encrypted[encrypted.Length - 1]; if (lastHexDigit == '0') { lastHexDigit = '1'; } else { lastHexDigit = '0'; } encrypted[encrypted.Length - 1] = (byte)lastHexDigit; target.Position = 0; target.Write(encrypted); } Assert.Throws <CryptographicException>( () => { using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } }); } }
/// <inheritdoc/> public override void Run(CommandLine commandLine) { if (commandLine.HasHelpOption) { Console.WriteLine(usage); Program.Exit(0); } var sourcePath = commandLine.Arguments.ElementAtOrDefault(0); var targetPath = commandLine.Arguments.ElementAtOrDefault(1); if (string.IsNullOrEmpty(sourcePath)) { Console.Error.WriteLine("*** ERROR: The SOURCE argument is required."); Program.Exit(1); } if (string.IsNullOrEmpty(targetPath)) { Console.Error.WriteLine("*** ERROR: The TARGET argument is required."); Program.Exit(1); } if (!NeonVault.IsEncrypted(sourcePath)) { Console.Error.WriteLine($"*** ERROR: The [{sourcePath}] file is not encrypted."); Program.Exit(1); } var vault = new NeonVault(Program.LookupPassword); vault.Decrypt(sourcePath, targetPath); Program.Exit(0); }
public void WrongPassword() { // Verify that we can detect that the wrong password was used. using (var tempFolder = new TempFolder()) { var vault = new NeonVault(passwordName => password1); var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); var targetPath = Path.Combine(tempFolder.Path, "target.txt"); File.WriteAllText(sourcePath, unencryptedText); vault.Encrypt(sourcePath, targetPath, "password-1"); using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } vault = new NeonVault(passwordName => password2); // This uses the wrong password. Assert.Throws <CryptographicException>( () => { using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { vault.Decrypt(target); } }); } }
public void NoPassword() { // Verify the proper exception when a named password cannot be found. var vault = new NeonVault(passwordName => throw new KeyNotFoundException()); using (var source = new MemoryStream(unencryptedBytes)) { Assert.Throws <CryptographicException>(() => vault.Encrypt(source, "password-1")); } // Verify the exception when an encrypted file references a password // that doesn't exist. using (var tempFolder = new TempFolder()) { vault = new NeonVault(passwordName => password1); var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); File.WriteAllText(sourcePath, unencryptedText); var encrypted = vault.Encrypt(sourcePath, "password-1"); vault = new NeonVault(passwordName => throw new KeyNotFoundException()); using (var source = new MemoryStream(encrypted)) { Assert.Throws <CryptographicException>(() => vault.Decrypt(source)); } } }
public void StreamToStream() { var vault = new NeonVault(GetPassword); using (var tempFolder = new TempFolder()) { var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); var targetPath = Path.Combine(tempFolder.Path, "target.txt"); File.WriteAllText(sourcePath, unencryptedText); using (var source = new FileStream(sourcePath, FileMode.Open, FileAccess.Read)) { using (var target = new FileStream(targetPath, FileMode.Create, FileAccess.ReadWrite)) { vault.Encrypt(source, target, "password-1"); } } using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } } }
/// <summary> /// Loads a file into the <see cref="Files"/> dictionary, using the file name /// (without the directory path) as the key. The file will be decrypted via /// <see cref="NeonVault"/> as necessary. /// </summary> /// <param name="path">The file path.</param> /// <param name="passwordProvider"> /// Optionally specifies the password provider function to be used to locate the /// password required to decrypt the source file when necessary. The password will /// use the <see cref="KubeHelper.LookupPassword(string)"/> method when /// <paramref name="passwordProvider"/> is <c>null</c>. /// </param> /// <exception cref="FileNotFoundException">Thrown if the file doesn't exist.</exception> /// <exception cref="FormatException">Thrown for file formatting problems.</exception> public void LoadFile(string path, Func <string, string> passwordProvider = null) { passwordProvider = passwordProvider ?? KubeHelper.LookupPassword; if (!File.Exists(path)) { throw new FileNotFoundException($"File not found: {path}"); } var vault = new NeonVault(passwordProvider); var bytes = vault.Decrypt(path); Files[Path.GetFileName(path)] = bytes; }
public void LowercaseHEX() { // Verify that we can process lower-case HEX digits. var vault = new NeonVault(GetPassword); using (var tempFolder = new TempFolder()) { var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); var targetPath = Path.Combine(tempFolder.Path, "target.txt"); var target2Path = Path.Combine(tempFolder.Path, "target2.txt"); File.WriteAllText(sourcePath, unencryptedText); vault.Encrypt(sourcePath, targetPath, "password-1"); using (var targetReader = new StreamReader(targetPath)) { using (var target2Writer = new StreamWriter(target2Path)) { // Copy the first line as-is and then write the remaining lines // as lowercase. var first = true; foreach (var line in targetReader.Lines()) { if (first) { target2Writer.WriteLine(line); first = false; } else { target2Writer.WriteLine(line.ToUpperInvariant()); } } } } using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } } }
/// <summary> /// <para> /// Loads environment variables formatted as <c>NAME=VALUE</c> from a text file into environment /// variables. The file will be decrypted using <see cref="NeonVault"/> if necessary. /// </para> /// <note> /// Blank lines and lines beginning with '#' will be ignored. /// </note> /// </summary> /// <param name="path">The input file path.</param> /// <param name="passwordProvider"> /// Optionally specifies the password provider function to be used to locate the /// password required to decrypt the source file when necessary. The password will /// use the <see cref="KubeHelper.LookupPassword(string)"/> method when /// <paramref name="passwordProvider"/> is <c>null</c>. /// </param> /// <exception cref="FileNotFoundException">Thrown if the file doesn't exist.</exception> /// <exception cref="FormatException">Thrown for file formatting problems.</exception> public void LoadEnvironment(string path, Func <string, string> passwordProvider = null) { passwordProvider = passwordProvider ?? KubeHelper.LookupPassword; if (!File.Exists(path)) { throw new FileNotFoundException($"File not found: {path}"); } var vault = new NeonVault(passwordProvider); var bytes = vault.Decrypt(path); using (var ms = new MemoryStream(bytes)) { using (var reader = new StreamReader(ms)) { var lineNumber = 1; foreach (var rawLine in reader.Lines()) { var line = rawLine.Trim(); if (line.Length == 0 || line.StartsWith("#")) { continue; } var fields = line.Split(new char[] { '=' }, 2); if (fields.Length != 2) { throw new FormatException($"[{path}:{lineNumber}]: Invalid input: {line}"); } var name = fields[0].Trim(); var value = fields[1].Trim(); if (name.Length == 0) { throw new FormatException($"[{path}:{lineNumber}]: Setting name cannot be blank."); } Environment.SetEnvironmentVariable(name, value); } } } }
public void StreamToBytes() { var vault = new NeonVault(GetPassword); var encrypted = (byte[])null; using (var source = new MemoryStream(unencryptedBytes)) { encrypted = vault.Encrypt(source, "password-1"); } using (var source = new MemoryStream(encrypted)) { var decrypted = vault.Decrypt(source); Assert.Equal(unencryptedBytes, decrypted); } }
/// <summary> /// Maps a logical configuration file path to an actual file on the /// local machine. This is used for unit testing to map a file on /// the local workstation to the path where the service expects the /// find to be. /// </summary> /// <param name="logicalPath">The logical file path (typically expressed as a Linux path).</param> /// <param name="physicalPath">The physical path to the file on the local workstation.</param> /// <param name="passwordProvider"> /// Optionally specifies the password provider function to be used to locate the /// password required to decrypt the source file when necessary. The password will /// use the <see cref="KubeHelper.LookupPassword(string)"/> method when /// <paramref name="passwordProvider"/> is <c>null</c>. /// </param> /// <exception cref="FileNotFoundException">Thrown if there's no file at <paramref name="physicalPath"/>.</exception> public void SetConfigFilePath(string logicalPath, string physicalPath, Func <string, string> passwordProvider = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(logicalPath), nameof(logicalPath)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(physicalPath), nameof(physicalPath)); if (!File.Exists(physicalPath)) { throw new FileNotFoundException($"Physical configuration file [{physicalPath}] does not exist."); } passwordProvider = passwordProvider ?? KubeHelper.LookupPassword; var vault = new NeonVault(passwordProvider); var bytes = vault.Decrypt(physicalPath); SetConfigFile(logicalPath, bytes); }
public void Unencrypted() { // Verify that "decrypting" an unencrypted file simply copies // the data to the output. var vault = new NeonVault(GetPassword); using (var tempFolder = new TempFolder()) { var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); var targetPath = Path.Combine(tempFolder.Path, "target.txt"); File.WriteAllText(sourcePath, unencryptedText); vault.Decrypt(sourcePath, targetPath); Assert.Equal(unencryptedText, File.ReadAllText(targetPath)); Assert.False(NeonVault.IsEncrypted(sourcePath)); Assert.False(NeonVault.IsEncrypted(targetPath)); } }
public void FileToBytes() { var vault = new NeonVault(GetPassword); var encrypted = (byte[])null; using (var tempFolder = new TempFolder()) { var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); File.WriteAllText(sourcePath, unencryptedText); encrypted = vault.Encrypt(sourcePath, "password-1"); using (var source = new MemoryStream(encrypted)) { var decrypted = vault.Decrypt(source); Assert.Equal(unencryptedBytes, decrypted); } } }
/// <inheritdoc/> public override async Task RunAsync(CommandLine commandLine) { if (commandLine.HasHelpOption) { Console.WriteLine(usage); Program.Exit(0); } var path = commandLine.Arguments.ElementAtOrDefault(0); if (string.IsNullOrEmpty(path)) { Console.Error.WriteLine("*** ERROR: The PATH argument is required."); Program.Exit(1); } if (!NeonVault.IsEncrypted(path, out var passwordName)) { Console.Error.WriteLine($"*** ERROR: The [{path}] file is not encrypted."); Program.Exit(1); } var vault = new NeonVault(Program.LookupPassword); var fileName = Path.GetFileName(path); using (var tempFolder = new TempFolder()) { var tempPath = Path.Combine(tempFolder.Path, fileName); // Decrypt the file to a secure temporary folder, launch the // editor and re-encrypt the file after the editor returns. vault.Decrypt(path, tempPath); NeonHelper.OpenEditor(tempPath); vault.Encrypt(tempPath, path, passwordName); } Program.Exit(0); await Task.CompletedTask; }
public void BOM() { // Verify that [NeonVault] can ignore UTF-8 BOM markers. var vault = new NeonVault(GetPassword); using (var tempFolder = new TempFolder()) { var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); var targetPath = Path.Combine(tempFolder.Path, "target.txt"); var target2Path = Path.Combine(tempFolder.Path, "target2.txt"); File.WriteAllText(sourcePath, unencryptedText); vault.Encrypt(sourcePath, targetPath, "password-1"); using (var targetReader = new StreamReader(targetPath)) { using (var target2 = new FileStream(target2Path, FileMode.Create, FileAccess.ReadWrite)) { // Write the BOM followed by the unmodified encrypted file lines. target2.Write(new byte[] { 0xEF, 0xBB, 0xBF }); // The BOM foreach (var line in targetReader.Lines()) { target2.Write(Encoding.ASCII.GetBytes(line + "\r\n")); } } } using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } } }
public void FileToFile() { var vault = new NeonVault(GetPassword); using (var tempFolder = new TempFolder()) { var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); var targetPath = Path.Combine(tempFolder.Path, "target.txt"); File.WriteAllText(sourcePath, unencryptedText); vault.Encrypt(sourcePath, targetPath, "password-1"); using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } Assert.False(NeonVault.IsEncrypted(sourcePath)); Assert.True(NeonVault.IsEncrypted(targetPath)); } }
public void VaultDecrypt() { using (var manager = new KubeTestManager()) { using (var tempFolder = new TempFolder()) { var orgDir = Environment.CurrentDirectory; Environment.CurrentDirectory = tempFolder.Path; NeonHelper.OpenEditorHandler = path => File.WriteAllText(path, plainText); try { using (var passwordFile = new TempFile(folder: KubeHelper.PasswordsFolder)) { File.WriteAllText(passwordFile.Path, testPassword); var vault = new NeonVault(passwordName => testPassword); using (var runner = new ProgramRunner()) { // Verify that the SOURCE argument is required. var result = runner.Execute(Program.Main, "vault", "decrypt"); Assert.NotEqual(0, result.ExitCode); Assert.Contains("*** ERROR: The SOURCE argument is required.", result.ErrorText); // Verify that the TARGET argument is required. result = runner.Execute(Program.Main, "vault", "decrypt"); Assert.NotEqual(0, result.ExitCode); Assert.Contains("*** ERROR: The SOURCE argument is required.", result.ErrorText); // Verify that the SOURCE-PATH argument is required. result = runner.Execute(Program.Main, "vault", "decrypt", "test.txt"); Assert.NotEqual(0, result.ExitCode); Assert.Contains("*** ERROR: The TARGET argument is required.", result.ErrorText); // Verify that we can create an encrypted file with an explicitly // named password. result = runner.Execute(Program.Main, "vault", "create", "test1.txt", passwordFile.Name); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test1.txt", out var passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test1.txt"))); // Verify that we can decrypt the file. result = runner.Execute(Program.Main, "vault", "decrypt", "test1.txt", "decrypted.txt"); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test1.txt", out passwordName)); Assert.True(!NeonVault.IsEncrypted("decrypted.txt", out passwordName)); Assert.Equal(plainText, File.ReadAllText("decrypted.txt")); } } } finally { Environment.CurrentDirectory = orgDir; NeonHelper.OpenEditorHandler = null; } } } }
/// <inheritdoc/> public override async Task RunAsync(CommandLine commandLine) { if (commandLine.HasHelpOption) { Console.WriteLine(usage); Program.Exit(0); } var splitCommandLine = commandLine.Split("--"); var leftCommandLine = splitCommandLine.Left; var rightCommandLine = splitCommandLine.Right; if (rightCommandLine == null || rightCommandLine.Arguments.Length == 0) { Console.Error.WriteLine("*** ERROR: Expected a command after a [--] argument."); Program.Exit(1); } // All arguments on the left command line should be VARIABLES files. // We're going to open each of these and set any enviroment variables // like [NAME=VALUE] we find. // // Note that these files may be encrypted. If any are, we'll decrypt // to a temporary file before we read them. foreach (var path in leftCommandLine.Arguments) { if (!File.Exists(path)) { Console.Error.WriteLine($"*** ERROR: File [{path}] does not exist."); Program.Exit(1); } DecryptWithAction(path, decryptedPath => { var lineNumber = 1; foreach (var line in File.ReadAllLines(decryptedPath)) { var trimmed = line.Trim(); if (line == string.Empty || line.StartsWith("#")) { continue; } var fields = line.Split('=', 2); if (fields.Length != 2 || fields[0] == string.Empty) { Console.Error.WriteLine($"*** ERROR: [{path}:{lineNumber}] is not formatted like: NAME=VALUE"); Program.Exit(1); } var name = fields[0].Trim(); var value = fields[1].Trim(); Environment.SetEnvironmentVariable(name, value); lineNumber++; } }); } // Any left command line options with a "--" prefix also specify environment variables. foreach (var option in leftCommandLine.Options.Where(o => o.Key.StartsWith("--"))) { Environment.SetEnvironmentVariable(option.Key.Substring(2), option.Value); } // We've read all of the variable files and left command line options // and initialized all environment variables. Now we need to process // and then execute the right command line. var tempFiles = new List <TempFile>(); try { var subcommand = rightCommandLine.Items; // Note that the first element of the subcommand specifies the // executable so we don't need to process that. for (int i = 1; i < subcommand.Length; i++) { var arg = subcommand[i]; if (arg.StartsWith("_...")) { // Argument is a reference to a potentially encrypted // file that needs to be passed decrypted. var path = arg.Substring(4); if (!File.Exists(path)) { Console.Error.WriteLine($"*** ERROR: File [{path}] does not exist."); Program.Exit(1); } if (NeonVault.IsEncrypted(path)) { var tempFile = new TempFile(); tempFiles.Add(tempFile); vault.Decrypt(path, tempFile.Path); path = tempFile.Path; } subcommand[i] = path; } else if (arg.StartsWith("_..")) { // Argument is a reference to a potentially encrypted text file // with environment variable references we'll need to update. var path = arg.Substring(3); if (!File.Exists(path)) { Console.Error.WriteLine($"*** ERROR: File [{path}] does not exist."); Program.Exit(1); } if (NeonVault.IsEncrypted(path)) { var tempFile = new TempFile(); tempFiles.Add(tempFile); vault.Decrypt(path, tempFile.Path); path = tempFile.Path; } subcommand[i] = path; // Perform the subsitutions. var unprocessed = File.ReadAllText(path); var processed = string.Empty; var linuxLineEndings = !unprocessed.Contains("\r\n"); using (var reader = new StreamReader(path)) { using (var preprocessor = new PreprocessReader(reader)) { preprocessor.ExpandVariables = true; preprocessor.LineEnding = linuxLineEndings ? LineEnding.LF : LineEnding.CRLF; preprocessor.ProcessStatements = false; preprocessor.StripComments = false; preprocessor.VariableExpansionRegex = PreprocessReader.AngleVariableExpansionRegex; processed = preprocessor.ReadToEnd(); } } File.WriteAllText(path, processed); } else if (arg.StartsWith("_.")) { // Argument is a reference to an environment variable. var name = arg.Substring(2); if (name == string.Empty) { Console.Error.WriteLine($"*** ERROR: Subcommand argument [{arg}] is not valid."); Program.Exit(1); } var value = Environment.GetEnvironmentVariable(name); if (value == null) { Console.Error.WriteLine($"*** ERROR: Subcommand argument [{arg}] references an undefined environment variable."); Program.Exit(2); } subcommand[i] = value; } else if (arg.StartsWith("-")) { // Argument is a command line option. We'll check to see if // it contains a reference to an environment variable. var valuePos = arg.IndexOf("=_."); if (valuePos != -1) { var optionPart = arg.Substring(0, valuePos); var name = arg.Substring(valuePos + 3); if (name == string.Empty) { Console.Error.WriteLine($"*** ERROR: Subcommand argument [{arg}] is not valid."); Program.Exit(1); } var value = Environment.GetEnvironmentVariable(name); if (value == null) { Console.Error.WriteLine($"*** ERROR: Subcommand argument [{arg}] references an undefined environment variable."); Program.Exit(1); } subcommand[i] = $"{optionPart}={value}"; } } else { // Otherwise, expand any envrionment variable references. subcommand[i] = Environment.ExpandEnvironmentVariables(subcommand[i]); } } // Execute the subcommand. var subcommandArgs = new List <object>(); foreach (var subcommandArg in subcommand) { subcommandArgs.Add(subcommandArg); } var exitCode = NeonHelper.Execute(subcommand[0], subcommandArgs.Skip(1).ToArray()); Program.Exit(exitCode); } finally { foreach (var tempFile in tempFiles) { tempFile.Dispose(); } } Program.Exit(0); await Task.CompletedTask; }
public void SwitchLineEndings() { // Verify that we can still decrypt en encrypted file after // changing the line endings from CRLF --> LF: var vault = new NeonVault(GetPassword, lineEnding: "\r\n"); using (var tempFolder = new TempFolder()) { var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); var targetPath = Path.Combine(tempFolder.Path, "target.txt"); var target2Path = Path.Combine(tempFolder.Path, "target2.txt"); File.WriteAllText(sourcePath, unencryptedText); vault.Encrypt(sourcePath, targetPath, "password-1"); using (var targetReader = new StreamReader(targetPath)) { using (var target2Writer = new StreamWriter(target2Path)) { foreach (var line in targetReader.Lines()) { target2Writer.WriteLine(line + "\n"); } } } using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } } // Verify that we can still decrypt en encrypted file after // changing the line endings from LF --> CRLF: vault = new NeonVault(GetPassword, lineEnding: "\n"); using (var tempFolder = new TempFolder()) { var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); var targetPath = Path.Combine(tempFolder.Path, "target.txt"); var target2Path = Path.Combine(tempFolder.Path, "target2.txt"); File.WriteAllText(sourcePath, unencryptedText); vault.Encrypt(sourcePath, targetPath, "password-1"); using (var targetReader = new StreamReader(targetPath)) { using (var target2Writer = new StreamWriter(target2Path)) { foreach (var line in targetReader.Lines()) { target2Writer.WriteLine(line + "\r\n"); } } } using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } } }
public void BadHEX() { var vault = new NeonVault(GetPassword); using (var tempFolder = new TempFolder()) { // Verify that an invalid character in the HEX part is detected. var sourcePath = Path.Combine(tempFolder.Path, "source.txt"); var targetPath = Path.Combine(tempFolder.Path, "target.txt"); File.WriteAllText(sourcePath, unencryptedText); vault.Encrypt(sourcePath, targetPath, "password-1"); using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } // Modify the last HEX digit to be the invalid HEX digit 'Z'. using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.ReadWrite)) { var encrypted = new byte[(int)target.Length]; target.Read(encrypted, 0, encrypted.Length); encrypted[encrypted.Length - 1] = (byte)'Z'; target.Position = 0; target.Write(encrypted); } Assert.Throws <CryptographicException>( () => { using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } }); // Verify that an odd number of HEX digits is detected by removing // the last character of the file. using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.ReadWrite)) { var encrypted = new byte[(int)target.Length]; target.Read(encrypted, 0, encrypted.Length); target.Position = 0; target.Write(encrypted, 0, encrypted.Length - 1); } Assert.Throws <CryptographicException>( () => { using (var target = new FileStream(targetPath, FileMode.Open, FileAccess.Read)) { var decrypted = vault.Decrypt(target); Assert.Equal(unencryptedBytes, decrypted); } }); } }
public async Task VaultEncrypt() { using (var testManager = new KubeTestManager()) { using (var tempFolder = new TempFolder()) { var orgDir = Environment.CurrentDirectory; Environment.CurrentDirectory = tempFolder.Path; NeonHelper.OpenEditorHandler = path => File.WriteAllText(path, plainText); try { using (var passwordFile = new TempFile(folder: KubeHelper.PasswordsFolder)) { File.WriteAllText(passwordFile.Path, testPassword); var vault = new NeonVault(passwordName => testPassword); using (var runner = new ProgramRunner()) { // Verify that the PATH argument is required. var result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "encrypt"); Assert.NotEqual(0, result.ExitCode); Assert.Contains("*** ERROR: The PATH argument is required.", result.ErrorText); // Verify that the TARGET argument is required when [--password-name] // or [--p] is not present. result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "decrypt", "source.txt"); Assert.NotEqual(0, result.ExitCode); Assert.Contains("*** ERROR: The TARGET argument is required.", result.ErrorText); // Verify that we can encrypt a file in-place, specifying an // explicit password name (using --password-name=NAME). File.WriteAllText("test1.txt", plainText); result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "encrypt", "test1.txt", $"--password-name={passwordFile.Name}"); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test1.txt", out var passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test1.txt"))); // Verify that we can encrypt a file in-place, specifying an // explicit password name (using --p=NAME). File.WriteAllText("test2.txt", plainText); result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "encrypt", "test2.txt", $"-p={passwordFile.Name}"); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test2.txt", out passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test2.txt"))); // Verify that we get an error trying to encrypt in-place without a // password name being explicitly specified and also without a // [.password-name] file present. File.WriteAllText("test3.txt", plainText); result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "encrypt", "test3.txt"); Assert.NotEqual(0, result.ExitCode); Assert.Contains("*** ERROR: A PASSWORD-NAME argument or [.password-name] file is required.", result.ErrorText); // Verify that we get an error trying to encrypt (not in-place) without a // password name being explicitly specified and also without a // [.password-name] file present. File.WriteAllText("test4.txt", plainText); result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "encrypt", "test4.txt", "test4.encypted.txt"); Assert.NotEqual(0, result.ExitCode); Assert.Contains("*** ERROR: A PASSWORD-NAME argument or [.password-name] file is required.", result.ErrorText); // Verify that we can encrypt a file to another with // and explicit password argument. File.WriteAllText("test5.txt", plainText); result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "encrypt", "test5.txt", "test5.encypted.txt", passwordName); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test5.encypted.txt", out passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test5.encypted.txt"))); // Verify that we can encrypt a file to another with // and explicit [--password-name] option. File.WriteAllText("test6.txt", plainText); result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "encrypt", "test6.txt", "test6.encypted.txt", $"--password-name={passwordName}"); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test6.encypted.txt", out passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test6.encypted.txt"))); // Verify that we can encrypt a file in-place using a [.password-name] file. File.WriteAllText("test7.txt", plainText); File.WriteAllText(".password-name", passwordName); result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "encrypt", "test7.txt"); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test7.txt", out passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test7.txt"))); // Verify that we can encrypt a file (not in-place) to another where // the source file is located in a different directory from the target // to ensure that we look for the [.password-name] file starting at // the target directory. using (var tempFile = new TempFile()) { File.WriteAllText(tempFile.Path, plainText); result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "encrypt", tempFile.Path, "test8.encrypted.txt"); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test8.encrypted.txt", out passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test8.encrypted.txt"))); } } } } finally { Environment.CurrentDirectory = orgDir; NeonHelper.OpenEditorHandler = null; } } } }
public async Task VaultPasswordName() { using (var testManager = new KubeTestManager()) { using (var tempFolder = new TempFolder()) { var orgDir = Environment.CurrentDirectory; Environment.CurrentDirectory = tempFolder.Path; NeonHelper.OpenEditorHandler = path => File.WriteAllText(path, plainText); try { using (var passwordFile = new TempFile(folder: KubeHelper.PasswordsFolder)) { File.WriteAllText(passwordFile.Path, testPassword); var vault = new NeonVault(passwordName => testPassword); using (var runner = new ProgramRunner()) { // Verify that the PATH argument is required. var result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "password-name"); Assert.NotEqual(0, result.ExitCode); Assert.Contains("*** ERROR: The PATH argument is required.", result.ErrorText); // Verify that we can create an encrypted file with an explicitly // named password. result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "create", "test1.txt", passwordFile.Name); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test1.txt", out var passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test1.txt"))); // Verify that we can get the password with a line ending. result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "password-name", "test1.txt", passwordFile.Name); Assert.Equal(0, result.ExitCode); Assert.Contains(passwordFile.Name, result.OutputText); Assert.Contains('\n', result.OutputText); // Verify that we can get the password without a line ending. result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "password-name", "-n", "test1.txt", passwordFile.Name); Assert.Equal(0, result.ExitCode); Assert.Equal(passwordFile.Name, result.OutputText); } } } finally { Environment.CurrentDirectory = orgDir; NeonHelper.OpenEditorHandler = null; } } } }
public async Task VaultCreate() { using (var testManager = new KubeTestManager()) { using (var tempFolder = new TempFolder()) { var orgDir = Environment.CurrentDirectory; Environment.CurrentDirectory = tempFolder.Path; NeonHelper.OpenEditorHandler = path => File.WriteAllText(path, plainText); try { using (var passwordFile = new TempFile(folder: KubeHelper.PasswordsFolder)) { File.WriteAllText(passwordFile.Path, testPassword); var vault = new NeonVault(passwordName => testPassword); using (var runner = new ProgramRunner()) { // Verify that the PATH argument is required. var result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "create"); Assert.NotEqual(0, result.ExitCode); Assert.Contains("*** ERROR: The PATH argument is required.", result.ErrorText); // Verify that the PASSWORD-NAME argument is required when there's // no default [.password-name] file. result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "create", "test1.txt"); Assert.NotEqual(0, result.ExitCode); Assert.Contains("*** ERROR: A PASSWORD-NAME argument or [.password-name] file is required.", result.ErrorText); // Verify that we can create an encrypted file with an explicitly // named password. result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "create", "test2.txt", passwordFile.Name); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test2.txt", out var passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test2.txt"))); // Verify that we see an error for a missing password. result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "create", "test3.txt", missingPasswordName); Assert.NotEqual(0, result.ExitCode); Assert.Contains($"*** ERROR: [System.Security.Cryptography.CryptographicException]: Password named [{missingPasswordName}] not found or is blank or whitespace.", result.ErrorText); // Verify that we see an error for an invalid password. result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "create", "test4.txt", badPasswordName); Assert.NotEqual(0, result.ExitCode); Assert.Contains($"*** ERROR: [System.Security.Cryptography.CryptographicException]: Password name [bad/password] contains invalid characters. Only ASCII letters, digits, underscores, dashs and dots are allowed.", result.ErrorText); // Verify that a local [.password-name] file is used successfully when we don't // explicitly pass a password name. File.WriteAllText(".password-name", passwordFile.Name); result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "create", "test5.txt"); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test5.txt", out passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test5.txt"))); // Verify that a [.password-name] file in the parent directory is used successfully // when we don't explicitly pass a password name. Directory.CreateDirectory("subfolder"); Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, "subfolder"); result = await runner.ExecuteAsync(Program.Main, "tool", "vault", "create", "test6.txt"); Assert.Equal(0, result.ExitCode); Assert.True(NeonVault.IsEncrypted("test6.txt", out passwordName)); Assert.Equal(passwordFile.Name, passwordName); Assert.Equal(plainText, Encoding.UTF8.GetString(vault.Decrypt("test6.txt"))); } } } finally { Environment.CurrentDirectory = orgDir; NeonHelper.OpenEditorHandler = null; } } } }