Smail/server/config/config_security_test.go

129 lines
3.4 KiB
Go

package config
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"testing"
)
func TestConfigCredentialsAreEncryptedAtRest(t *testing.T) {
oldRoot := ROOT_PATH
root := t.TempDir()
ROOT_PATH = root + string(os.PathSeparator)
t.Cleanup(func() { ROOT_PATH = oldRoot })
original := &Config{
WeChatPushSecret: "wechat-secret-value",
TgBotToken: "telegram-secret-value",
WebPushToken: "web-push-secret-value",
}
if err := WriteConfig(original); err != nil {
t.Fatal(err)
}
raw, err := os.ReadFile(filepath.Join(root, "config", "config.json"))
if err != nil {
t.Fatal(err)
}
for _, secret := range []string{original.WeChatPushSecret, original.TgBotToken, original.WebPushToken} {
if bytes.Contains(raw, []byte(secret)) {
t.Fatal("plaintext credential was written to configuration")
}
}
loaded, err := ReadConfig()
if err != nil {
t.Fatal(err)
}
if loaded.WeChatPushSecret != original.WeChatPushSecret || loaded.TgBotToken != original.TgBotToken || loaded.WebPushToken != original.WebPushToken {
t.Fatal("configuration credentials did not round trip")
}
if original.WeChatPushSecret != "wechat-secret-value" {
t.Fatal("write mutated the caller configuration")
}
}
func TestConfigPrefixCredentialRoundTrip(t *testing.T) {
oldRoot := ROOT_PATH
root := t.TempDir()
ROOT_PATH = root + string(os.PathSeparator)
t.Cleanup(func() { ROOT_PATH = oldRoot })
original := &Config{WeChatPushSecret: "enc:v1:provider-secret"}
if err := WriteConfig(original); err != nil {
t.Fatal(err)
}
loaded, err := ReadConfig()
if err != nil {
t.Fatal(err)
}
if loaded.WeChatPushSecret != original.WeChatPushSecret {
t.Fatal("prefix credential did not round trip through configuration")
}
}
func TestReadConfigRejectsLegacyPlaintextCredentials(t *testing.T) {
oldRoot := ROOT_PATH
root := t.TempDir()
ROOT_PATH = root + string(os.PathSeparator)
t.Cleanup(func() { ROOT_PATH = oldRoot })
legacy := &Config{
WeChatPushSecret: "legacy-wechat-secret",
TgBotToken: "legacy-telegram-token",
WebPushToken: "legacy-web-push-token",
}
raw, err := json.Marshal(legacy)
if err != nil {
t.Fatal(err)
}
if err = writeConfigFile("config.json", raw); err != nil {
t.Fatal(err)
}
if _, err := ReadConfig(); err == nil {
t.Fatal("legacy plaintext credential was accepted")
}
stored, err := os.ReadFile(filepath.Join(root, "config", "config.json"))
if err != nil {
t.Fatal(err)
}
for _, plaintext := range []string{legacy.WeChatPushSecret, legacy.TgBotToken, legacy.WebPushToken} {
if !bytes.Contains(stored, []byte(plaintext)) {
t.Fatal("rejected configuration was modified")
}
}
}
func TestConfigRejectsTamperedCredential(t *testing.T) {
oldRoot := ROOT_PATH
root := t.TempDir()
ROOT_PATH = root + string(os.PathSeparator)
t.Cleanup(func() { ROOT_PATH = oldRoot })
if err := WriteConfig(&Config{WeChatPushSecret: "wechat-secret-value"}); err != nil {
t.Fatal(err)
}
path := filepath.Join(root, "config", "config.json")
raw, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
marker := []byte("enc:v1:")
index := bytes.Index(raw, marker)
if index < 0 {
t.Fatal("encrypted credential marker was not found")
}
change := index + len(marker)
if raw[change] == 'A' {
raw[change] = 'B'
} else {
raw[change] = 'A'
}
if err := os.WriteFile(path, raw, 0600); err != nil {
t.Fatal(err)
}
if _, err := ReadConfig(); err == nil {
t.Fatal("tampered credential was accepted")
}
}