package config import ( "bytes" "fmt" "io" "io/ioutil" "os" "path/filepath" "reflect" "testing" ) func TestPromptForConfigEncryption(t *testing.T) { t.Parallel() confirm, err := promptForConfigEncryption() if confirm { t.Error("promptForConfigEncryption return incorrect bool") } if err == nil { t.Error("Expected error as there is no input") } } func TestPromptForConfigKey(t *testing.T) { t.Parallel() byteyBite, err := PromptForConfigKey(true) if err == nil && len(byteyBite) > 1 { t.Errorf("PromptForConfigKey: %s", err) } _, err = PromptForConfigKey(false) if err == nil { t.Error("Expected error") } } func TestEncryptConfigFile(t *testing.T) { _, err := EncryptConfigFile([]byte("test"), nil) if err == nil { t.Fatal("Expected error") } c := &Config{ sessionDK: []byte("a"), } _, err = c.encryptConfigFile([]byte("test")) if err == nil { t.Fatal("Expected error") } sessDk, salt, err := makeNewSessionDK([]byte("asdf")) if err != nil { t.Fatal(err) } c = &Config{ sessionDK: sessDk, storedSalt: salt, } _, err = c.encryptConfigFile([]byte("test")) if err != nil { t.Fatal(err) } } func TestDecryptConfigFile(t *testing.T) { result, err := EncryptConfigFile([]byte("test"), []byte("key")) if err != nil { t.Fatal(err) } _, err = DecryptConfigFile(result, nil) if err == nil { t.Fatal("Expected error") } _, err = DecryptConfigFile([]byte("test"), nil) if err == nil { t.Fatal("Expected error") } _, err = DecryptConfigFile([]byte("test"), []byte("AAAAAAAAAAAAAAAA")) if err == nil { t.Fatalf("Expected %s", errAESBlockSize) } result, err = EncryptConfigFile([]byte("test"), []byte("key")) if err != nil { t.Fatal(err) } _, err = DecryptConfigFile(result, []byte("key")) if err != nil { t.Fatal(err) } } func TestConfirmECS(t *testing.T) { t.Parallel() ECStest := []byte(EncryptConfirmString) if !ConfirmECS(ECStest) { t.Errorf("TestConfirmECS: Error finding ECS.") } } func TestRemoveECS(t *testing.T) { t.Parallel() ECStest := []byte(EncryptConfirmString) reader := bytes.NewReader(ECStest) err := skipECS(reader) if err != nil { t.Error(err) } // Attempt read var buf []byte _, err = reader.Read(buf) if err != io.EOF { t.Errorf("TestConfirmECS: Error ECS not deleted.") } } func TestMakeNewSessionDK(t *testing.T) { t.Parallel() _, _, err := makeNewSessionDK(nil) if err == nil { t.Fatal("makeNewSessionDK passed with nil key") } } func TestEncryptTwiceReusesSaltButNewCipher(t *testing.T) { c := &Config{} c.EncryptConfig = 1 tempDir, err := ioutil.TempDir("", "") if err != nil { t.Fatalf("Problem creating temp dir at %s: %s\n", tempDir, err) } defer os.RemoveAll(tempDir) // Prepare input passFile, err := ioutil.TempFile(tempDir, "*.pw") if err != nil { t.Fatalf("Problem creating temp file at %s: %s\n", tempDir, err) } passFile.WriteString("pass\npass\n") passFile.Close() // Temporarily replace Stdin with a custom input oldIn := os.Stdin defer func() { os.Stdin = oldIn }() os.Stdin, err = os.Open(passFile.Name()) if err != nil { t.Fatalf("Problem opening temp file at %s: %s\n", passFile.Name(), err) } // Save encrypted config enc1 := filepath.Join(tempDir, "encrypted.dat") err = c.SaveConfigToFile(enc1) if err != nil { t.Fatalf("Problem storing config in file %s: %s\n", enc1, err) } // Save again enc2 := filepath.Join(tempDir, "encrypted2.dat") err = c.SaveConfigToFile(enc2) if err != nil { t.Fatalf("Problem storing config in file %s: %s\n", enc2, err) } data1, err := ioutil.ReadFile(enc1) if err != nil { t.Fatalf("Problem reading file %s: %s\n", enc1, err) } data2, err := ioutil.ReadFile(enc2) if err != nil { t.Fatalf("Problem reading file %s: %s\n", enc2, err) } // legth of prefix + salt l := len(EncryptConfirmString+SaltPrefix) + SaltRandomLength // Even though prefix, including salt with the random bytes is the same if !bytes.Equal(data1[:l], data2[:l]) { t.Error("Salt is not reused.") } // the cipher text should not be if bytes.Equal(data1, data2) { t.Error("Encryption key must have been reused as cipher texts are the same") } } func TestSaveAndReopenEncryptedConfig(t *testing.T) { c := &Config{} c.Name = "myCustomName" c.EncryptConfig = 1 tempDir, err := ioutil.TempDir("", "") if err != nil { t.Fatalf("Problem creating temp dir at %s: %s\n", tempDir, err) } defer os.RemoveAll(tempDir) // Save encrypted config enc := filepath.Join(tempDir, "encrypted.dat") err = withInteractiveResponse(t, "pass\npass\n", func() error { return c.SaveConfigToFile(enc) }) if err != nil { t.Fatalf("Problem storing config in file %s: %s\n", enc, err) } readConf := &Config{} err = withInteractiveResponse(t, "pass\n", func() error { // Load with no existing state, key is read from the prepared file return readConf.ReadConfigFromFile(enc, true) }) // Verify if err != nil { t.Fatalf("Problem reading config in file %s: %s\n", enc, err) } if c.Name != readConf.Name || c.EncryptConfig != readConf.EncryptConfig { t.Error("Loaded conf not the same as original") } } // setAnswersFile sets the given file as the current stdin // returns the close function to defer for reverting the stdin func setAnswersFile(t *testing.T, answerFile string) func() { oldIn := os.Stdin inputFile, err := os.Open(answerFile) if err != nil { t.Fatalf("Problem opening temp file at %s: %s\n", answerFile, err) } os.Stdin = inputFile return func() { inputFile.Close() os.Stdin = oldIn } } func TestReadConfigWithPrompt(t *testing.T) { // Prepare temp dir tempDir, err := ioutil.TempDir("", "") if err != nil { t.Fatalf("Problem creating temp dir at %s: %s\n", tempDir, err) } defer os.RemoveAll(tempDir) // Ensure we'll get the prompt when loading c := &Config{ EncryptConfig: 0, } // Save config testConfigFile := filepath.Join(tempDir, "config.json") err = c.SaveConfigToFile(testConfigFile) if err != nil { t.Fatalf("Problem saving config file in %s: %s\n", tempDir, err) } // Run the test c = &Config{} err = withInteractiveResponse(t, "y\npass\npass\n", func() error { return c.ReadConfigFromFile(testConfigFile, false) }) if err != nil { t.Fatalf("Problem reading config file at %s: %s\n", testConfigFile, err) } // Verify results data, err := ioutil.ReadFile(testConfigFile) if err != nil { t.Fatalf("Problem reading saved file at %s: %s\n", testConfigFile, err) } if c.EncryptConfig != fileEncryptionEnabled { t.Error("Config encryption flag should be set after prompts") } if !ConfirmECS(data) { t.Error("Config file should be encrypted after prompts") } } func TestReadEncryptedConfigFromReader(t *testing.T) { keyProvider := func() ([]byte, error) { return []byte("pass"), nil } // Encrypted conf for: `{"name":"test"}` with key `pass` confBytes := []byte{84, 72, 79, 82, 83, 45, 72, 65, 77, 77, 69, 82, 126, 71, 67, 84, 126, 83, 79, 126, 83, 65, 76, 84, 89, 126, 246, 110, 128, 3, 30, 168, 172, 160, 198, 176, 136, 62, 152, 155, 253, 176, 16, 48, 52, 246, 44, 29, 151, 47, 217, 226, 178, 12, 218, 113, 248, 172, 195, 232, 136, 104, 9, 199, 20, 4, 71, 4, 253, 249} conf, encrypted, err := ReadConfig(bytes.NewReader(confBytes), keyProvider) if err != nil { t.Errorf("TestReadConfig %s", err) } if !encrypted { t.Errorf("Expected encrypted config %s", err) } if conf.Name != "test" { t.Errorf("Conf not properly loaded %s", err) } // Change the salt confBytes[20] = 0 conf, _, err = ReadConfig(bytes.NewReader(confBytes), keyProvider) if err == nil { t.Errorf("Expected unable to decrypt, but got %+v", conf) } } // TestSaveConfigToFileWithErrorInPasswordPrompt should preserve the original file func TestSaveConfigToFileWithErrorInPasswordPrompt(t *testing.T) { c := &Config{ Name: "test", EncryptConfig: fileEncryptionEnabled, } testData := []byte("testdata") f, err := ioutil.TempFile("", "") if err != nil { t.Fatal(err) } targetFile := f.Name() defer os.Remove(targetFile) _, err = io.Copy(f, bytes.NewReader(testData)) if err != nil { t.Fatal(err) } err = f.Close() if err != nil { t.Fatal(err) } err = withInteractiveResponse(t, "\n\n", func() error { err = c.SaveConfigToFile(targetFile) if err == nil { t.Error("Expected error") } return nil }) if err != nil { t.Fatal(err) } data, err := ioutil.ReadFile(targetFile) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(data, testData) { t.Errorf("Expected contents %s, but was %s", testData, data) } } func withInteractiveResponse(t *testing.T, response string, body func() error) error { // Answers to the prompt responseFile, err := ioutil.TempFile("", "*.in") if err != nil { return fmt.Errorf("problem creating temp file: %w", err) } _, err = responseFile.WriteString(response) if err != nil { return fmt.Errorf("problem writing to temp file at %s: %w", responseFile.Name(), err) } err = responseFile.Close() if err != nil { return fmt.Errorf("problem closing temp file at %s: %w", responseFile.Name(), err) } defer os.Remove(responseFile.Name()) // Temporarily replace Stdin with a custom input cleanup := setAnswersFile(t, responseFile.Name()) defer cleanup() return body() }