package service import ( "fmt" "strings" "hy2xs-admin/dao" "hy2xs-admin/util" ) // Криптоматериал пиров берётся из dao, а не создаётся здесь заново. // // Что было. В этом файле лежали СОБСТВЕННЫЕ getOrCreateConfigKey и // getPeerSecretEncryptionKey — построчные копии одноимённых функций из // dao/sqlite.go. Две реализации, порождающие один и тот же материал шифрования, // в двух пакетах: bootstrap-пир получал ключ через dao, а всё остальное — через // service. Пока обе читали один ключ из таблицы `config`, поведение совпадало, // но любое расхождение означало бы, что секреты пиров шифруются одним ключом, а // расшифровываются другим, и проявилось бы это на живых пирах. // // Копия в service несла ещё и порядок «сначала INSERT, при ошибке UPDATE», // из-за которого каждая первая загрузка печатала в журнал // `duplicated key not allowed` уровня error на здоровом старте. // GetPeerSecretKey — HMAC-ключ, которым считается secret_digest пира. func GetPeerSecretKey() (string, error) { return dao.GetOrCreatePeerSecretDigestKey() } func PeerSecretDigest(rawSecret string) (string, error) { secretKey, err := GetPeerSecretKey() if err != nil { return "", err } return util.HmacSHA256Hex(rawSecret, secretKey), nil } func EncryptPeerSecret(rawSecret string) (string, error) { key, err := dao.GetOrCreatePeerSecretEncryptionKey() if err != nil { return "", err } return util.EncryptAESGCM(rawSecret, key) } // DecryptPeerSecret расшифровывает сохранённый секрет пира. // // Формат хранения ровно один: `v1:` + AES-GCM. Значение без этого префикса — // не «секрет в старом формате», а повреждённые данные, и ответом на них // является ошибка. // // Что было: // // if !strings.HasPrefix(stored, "v1:") { // return stored, nil // } // // то есть содержимое колонки возвращалось как якобы успешно расшифрованный // секрет. Ветка досталась от поколения, в котором секреты пиров лежали в базе // открытым текстом; при clean-install-only политике такой строки не может // существовать — EncryptPeerSecret всегда пишет префикс, — а вред остаётся: // // повреждённая колонка → мусор уходит в клиентскую ссылку как секрет; // резервная копия с секретами → мусор попадает в файл вместо credentials; // значение, записанное в обход → принимается без единой проверки. // // Это тот же класс, что и удалённый SHA-224 fallback при входе: молчаливое // «понимаем формат предыдущего поколения» превращается в молчаливое «понимаем // что угодно». func DecryptPeerSecret(stored string) (string, error) { if !strings.HasPrefix(stored, peerSecretCipherPrefix) { return "", fmt.Errorf( "секрет пира хранится в неизвестном формате: ожидался префикс %q. "+ "HY2XS хранит секреты пиров только зашифрованными", peerSecretCipherPrefix, ) } key, err := dao.GetOrCreatePeerSecretEncryptionKey() if err != nil { return "", err } return util.DecryptAESGCM(stored, key) } // peerSecretCipherPrefix — единственный поддерживаемый формат хранения. // Значение задаёт util.EncryptAESGCM; здесь оно объявлено, чтобы проверка и // сообщение об ошибке не расходились с ним по разным файлам молча. const peerSecretCipherPrefix = "v1:"