MUMBLE "failed to get access using private key": чекер держит share-токен, выданный ещё старым keygen, а после перехода на HMAC ValidateKey его отвергал. - keygen/legacy.go: восстановленный из keygen.a алгоритм seed=(seed*17+42)%62 принимается ТОЛЬКО для video.id <= cutoff, то есть для видео, существовавших на момент перехода. Выше cutoff -- лишь HMAC. - cutoff берётся из max(video.id) при первом старте и пишется в public/.legacy_cutoff на volume: рестарт не должен расширять окно. Не смогли прочитать -- fail closed, legacy выключен. - Когда старые флаги протухнут: echo 0 > public/.legacy_cutoff + рестарт, и legacy отключается полностью. Ещё MUMBLE "cannot create video" -- он же DoS, у NOP-команды то же самое: - checkGeometry сделан FAIL-OPEN. Неразобранный ffprobe больше не отклоняет загрузку, режем только успешно прочитанную и абсурдную геометрию. - ffmpeg -max_alloc 128M: 1080p кадру нужно ~8 МБ, бомбе ~1 ГБ. Работает даже когда probe промолчал. check_tiktak.sh: smoke-тест для vulnbox, гоняет сценарий чекера целиком и отдельно проверяет, что патчи реально в задеплоенном билде. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
48 lines
1.6 KiB
Go
48 lines
1.6 KiB
Go
package keygen
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"strconv"
|
|
)
|
|
|
|
// The legacy C implementation (legacy/keygen.a, no source shipped) derived the
|
|
// share token from the video id ALONE -- no key, no salt. Worse, its state
|
|
// collapsed to (seed*17+42)%62 after the first round, so the token depended
|
|
// only on id%62: 62 distinct tokens for the whole service. Anyone could upload
|
|
// 62 private videos, copy the tokens off their own /home and unlock every
|
|
// private video on every team's box. Replaced with a keyed HMAC.
|
|
//
|
|
// Same signatures, same 30-char base62 alphabet, so nothing else changes.
|
|
// keygen.h / legacy/keygen.a are no longer part of the build (cgo is gone).
|
|
const (
|
|
tokenLen = 30
|
|
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
|
|
// Server-side secret. Rotating it invalidates every token already handed
|
|
// out, so change it only between rounds.
|
|
secret = "D1gkxNv7_HphLJjNuFag_stjaByXLZ1Y0l12RGppZtw"
|
|
)
|
|
|
|
func GenerateKey(vid int) string {
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
mac.Write([]byte(strconv.Itoa(vid)))
|
|
sum := mac.Sum(nil)
|
|
|
|
out := make([]byte, tokenLen)
|
|
for i := 0; i < tokenLen; i++ {
|
|
out[i] = alphabet[int(sum[i%len(sum)])%len(alphabet)]
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
func ValidateKey(token string, vid int) bool {
|
|
// Constant-time: never leak how much of the token was correct.
|
|
if hmac.Equal([]byte(token), []byte(GenerateKey(vid))) {
|
|
return true
|
|
}
|
|
// Grandfather in tokens handed out by the legacy algorithm, but only for
|
|
// videos that already existed when we switched. See legacy.go.
|
|
return legacyAccepted(token, vid)
|
|
}
|